This adds the ability to have multiple concurrent connections to a single device. This is primarily useful when the network has multiple physical links for aggregated bandwidth. A single connection will never see a higher rate than a single link can give, but multiple connections are load-balanced over multiple links. It is also incidentally useful for older multi-core CPUs, where bandwidth could be limited by the TLS performance of a single CPU core -- using multiple connections achieves concurrency in the required crypto calculations... Co-authored-by: Simon Frei <freisim93@gmail.com> Co-authored-by: tomasz1986 <twilczynski@naver.com> Co-authored-by: bt90 <btom1990@googlemail.com>
This commit is contained in:
co-authored by
Simon Frei
tomasz1986
bt90
parent
38bbdebffa
commit
c6334e61aa
@@ -34,6 +34,7 @@ func newFakeConnection(id protocol.DeviceID, model Model) *fakeConnection {
|
||||
return f.fileData[name], nil
|
||||
})
|
||||
f.DeviceIDReturns(id)
|
||||
f.ConnectionIDReturns(rand.String(16))
|
||||
f.CloseCalls(func(err error) {
|
||||
f.closeOnce.Do(func() {
|
||||
close(f.closed)
|
||||
|
||||
@@ -530,7 +530,7 @@ func setupROFolder(t *testing.T) (*testModel, *receiveOnlyFolder, context.Cancel
|
||||
cfg.Folders = []config.FolderConfiguration{fcfg}
|
||||
replace(t, w, cfg)
|
||||
|
||||
m := newModel(t, w, myID, "syncthing", "dev", nil)
|
||||
m := newModel(t, w, myID, nil)
|
||||
m.ServeBackground()
|
||||
<-m.started
|
||||
must(t, m.ScanFolder("ro"))
|
||||
|
||||
@@ -507,7 +507,7 @@ nextFile:
|
||||
|
||||
devices := snap.Availability(fileName)
|
||||
for _, dev := range devices {
|
||||
if _, ok := f.model.Connection(dev); ok {
|
||||
if f.model.ConnectedTo(dev) {
|
||||
// Handle the file normally, by copying and pulling, etc.
|
||||
f.handleFile(fi, snap, copyChan)
|
||||
continue nextFile
|
||||
|
||||
+48
-192
@@ -76,18 +76,16 @@ type Model struct {
|
||||
result1 model.FolderCompletion
|
||||
result2 error
|
||||
}
|
||||
ConnectionStub func(protocol.DeviceID) (protocol.Connection, bool)
|
||||
connectionMutex sync.RWMutex
|
||||
connectionArgsForCall []struct {
|
||||
ConnectedToStub func(protocol.DeviceID) bool
|
||||
connectedToMutex sync.RWMutex
|
||||
connectedToArgsForCall []struct {
|
||||
arg1 protocol.DeviceID
|
||||
}
|
||||
connectionReturns struct {
|
||||
result1 protocol.Connection
|
||||
result2 bool
|
||||
connectedToReturns struct {
|
||||
result1 bool
|
||||
}
|
||||
connectionReturnsOnCall map[int]struct {
|
||||
result1 protocol.Connection
|
||||
result2 bool
|
||||
connectedToReturnsOnCall map[int]struct {
|
||||
result1 bool
|
||||
}
|
||||
ConnectionStatsStub func() map[string]interface{}
|
||||
connectionStatsMutex sync.RWMutex
|
||||
@@ -262,17 +260,6 @@ type Model struct {
|
||||
result1 map[string][]versioner.FileVersion
|
||||
result2 error
|
||||
}
|
||||
GetHelloStub func(protocol.DeviceID) protocol.HelloIntf
|
||||
getHelloMutex sync.RWMutex
|
||||
getHelloArgsForCall []struct {
|
||||
arg1 protocol.DeviceID
|
||||
}
|
||||
getHelloReturns struct {
|
||||
result1 protocol.HelloIntf
|
||||
}
|
||||
getHelloReturnsOnCall map[int]struct {
|
||||
result1 protocol.HelloIntf
|
||||
}
|
||||
GetMtimeMappingStub func(string, string) (fs.MtimeMapping, error)
|
||||
getMtimeMappingMutex sync.RWMutex
|
||||
getMtimeMappingArgsForCall []struct {
|
||||
@@ -378,16 +365,6 @@ type Model struct {
|
||||
result3 []db.FileInfoTruncated
|
||||
result4 error
|
||||
}
|
||||
NumConnectionsStub func() int
|
||||
numConnectionsMutex sync.RWMutex
|
||||
numConnectionsArgsForCall []struct {
|
||||
}
|
||||
numConnectionsReturns struct {
|
||||
result1 int
|
||||
}
|
||||
numConnectionsReturnsOnCall map[int]struct {
|
||||
result1 int
|
||||
}
|
||||
OnHelloStub func(protocol.DeviceID, net.Addr, protocol.Hello) error
|
||||
onHelloMutex sync.RWMutex
|
||||
onHelloArgsForCall []struct {
|
||||
@@ -888,68 +865,65 @@ func (fake *Model) CompletionReturnsOnCall(i int, result1 model.FolderCompletion
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *Model) Connection(arg1 protocol.DeviceID) (protocol.Connection, bool) {
|
||||
fake.connectionMutex.Lock()
|
||||
ret, specificReturn := fake.connectionReturnsOnCall[len(fake.connectionArgsForCall)]
|
||||
fake.connectionArgsForCall = append(fake.connectionArgsForCall, struct {
|
||||
func (fake *Model) ConnectedTo(arg1 protocol.DeviceID) bool {
|
||||
fake.connectedToMutex.Lock()
|
||||
ret, specificReturn := fake.connectedToReturnsOnCall[len(fake.connectedToArgsForCall)]
|
||||
fake.connectedToArgsForCall = append(fake.connectedToArgsForCall, struct {
|
||||
arg1 protocol.DeviceID
|
||||
}{arg1})
|
||||
stub := fake.ConnectionStub
|
||||
fakeReturns := fake.connectionReturns
|
||||
fake.recordInvocation("Connection", []interface{}{arg1})
|
||||
fake.connectionMutex.Unlock()
|
||||
stub := fake.ConnectedToStub
|
||||
fakeReturns := fake.connectedToReturns
|
||||
fake.recordInvocation("ConnectedTo", []interface{}{arg1})
|
||||
fake.connectedToMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1, ret.result2
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1, fakeReturns.result2
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *Model) ConnectionCallCount() int {
|
||||
fake.connectionMutex.RLock()
|
||||
defer fake.connectionMutex.RUnlock()
|
||||
return len(fake.connectionArgsForCall)
|
||||
func (fake *Model) ConnectedToCallCount() int {
|
||||
fake.connectedToMutex.RLock()
|
||||
defer fake.connectedToMutex.RUnlock()
|
||||
return len(fake.connectedToArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *Model) ConnectionCalls(stub func(protocol.DeviceID) (protocol.Connection, bool)) {
|
||||
fake.connectionMutex.Lock()
|
||||
defer fake.connectionMutex.Unlock()
|
||||
fake.ConnectionStub = stub
|
||||
func (fake *Model) ConnectedToCalls(stub func(protocol.DeviceID) bool) {
|
||||
fake.connectedToMutex.Lock()
|
||||
defer fake.connectedToMutex.Unlock()
|
||||
fake.ConnectedToStub = stub
|
||||
}
|
||||
|
||||
func (fake *Model) ConnectionArgsForCall(i int) protocol.DeviceID {
|
||||
fake.connectionMutex.RLock()
|
||||
defer fake.connectionMutex.RUnlock()
|
||||
argsForCall := fake.connectionArgsForCall[i]
|
||||
func (fake *Model) ConnectedToArgsForCall(i int) protocol.DeviceID {
|
||||
fake.connectedToMutex.RLock()
|
||||
defer fake.connectedToMutex.RUnlock()
|
||||
argsForCall := fake.connectedToArgsForCall[i]
|
||||
return argsForCall.arg1
|
||||
}
|
||||
|
||||
func (fake *Model) ConnectionReturns(result1 protocol.Connection, result2 bool) {
|
||||
fake.connectionMutex.Lock()
|
||||
defer fake.connectionMutex.Unlock()
|
||||
fake.ConnectionStub = nil
|
||||
fake.connectionReturns = struct {
|
||||
result1 protocol.Connection
|
||||
result2 bool
|
||||
}{result1, result2}
|
||||
func (fake *Model) ConnectedToReturns(result1 bool) {
|
||||
fake.connectedToMutex.Lock()
|
||||
defer fake.connectedToMutex.Unlock()
|
||||
fake.ConnectedToStub = nil
|
||||
fake.connectedToReturns = struct {
|
||||
result1 bool
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *Model) ConnectionReturnsOnCall(i int, result1 protocol.Connection, result2 bool) {
|
||||
fake.connectionMutex.Lock()
|
||||
defer fake.connectionMutex.Unlock()
|
||||
fake.ConnectionStub = nil
|
||||
if fake.connectionReturnsOnCall == nil {
|
||||
fake.connectionReturnsOnCall = make(map[int]struct {
|
||||
result1 protocol.Connection
|
||||
result2 bool
|
||||
func (fake *Model) ConnectedToReturnsOnCall(i int, result1 bool) {
|
||||
fake.connectedToMutex.Lock()
|
||||
defer fake.connectedToMutex.Unlock()
|
||||
fake.ConnectedToStub = nil
|
||||
if fake.connectedToReturnsOnCall == nil {
|
||||
fake.connectedToReturnsOnCall = make(map[int]struct {
|
||||
result1 bool
|
||||
})
|
||||
}
|
||||
fake.connectionReturnsOnCall[i] = struct {
|
||||
result1 protocol.Connection
|
||||
result2 bool
|
||||
}{result1, result2}
|
||||
fake.connectedToReturnsOnCall[i] = struct {
|
||||
result1 bool
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *Model) ConnectionStats() map[string]interface{} {
|
||||
@@ -1797,67 +1771,6 @@ func (fake *Model) GetFolderVersionsReturnsOnCall(i int, result1 map[string][]ve
|
||||
}{result1, result2}
|
||||
}
|
||||
|
||||
func (fake *Model) GetHello(arg1 protocol.DeviceID) protocol.HelloIntf {
|
||||
fake.getHelloMutex.Lock()
|
||||
ret, specificReturn := fake.getHelloReturnsOnCall[len(fake.getHelloArgsForCall)]
|
||||
fake.getHelloArgsForCall = append(fake.getHelloArgsForCall, struct {
|
||||
arg1 protocol.DeviceID
|
||||
}{arg1})
|
||||
stub := fake.GetHelloStub
|
||||
fakeReturns := fake.getHelloReturns
|
||||
fake.recordInvocation("GetHello", []interface{}{arg1})
|
||||
fake.getHelloMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub(arg1)
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *Model) GetHelloCallCount() int {
|
||||
fake.getHelloMutex.RLock()
|
||||
defer fake.getHelloMutex.RUnlock()
|
||||
return len(fake.getHelloArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *Model) GetHelloCalls(stub func(protocol.DeviceID) protocol.HelloIntf) {
|
||||
fake.getHelloMutex.Lock()
|
||||
defer fake.getHelloMutex.Unlock()
|
||||
fake.GetHelloStub = stub
|
||||
}
|
||||
|
||||
func (fake *Model) GetHelloArgsForCall(i int) protocol.DeviceID {
|
||||
fake.getHelloMutex.RLock()
|
||||
defer fake.getHelloMutex.RUnlock()
|
||||
argsForCall := fake.getHelloArgsForCall[i]
|
||||
return argsForCall.arg1
|
||||
}
|
||||
|
||||
func (fake *Model) GetHelloReturns(result1 protocol.HelloIntf) {
|
||||
fake.getHelloMutex.Lock()
|
||||
defer fake.getHelloMutex.Unlock()
|
||||
fake.GetHelloStub = nil
|
||||
fake.getHelloReturns = struct {
|
||||
result1 protocol.HelloIntf
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *Model) GetHelloReturnsOnCall(i int, result1 protocol.HelloIntf) {
|
||||
fake.getHelloMutex.Lock()
|
||||
defer fake.getHelloMutex.Unlock()
|
||||
fake.GetHelloStub = nil
|
||||
if fake.getHelloReturnsOnCall == nil {
|
||||
fake.getHelloReturnsOnCall = make(map[int]struct {
|
||||
result1 protocol.HelloIntf
|
||||
})
|
||||
}
|
||||
fake.getHelloReturnsOnCall[i] = struct {
|
||||
result1 protocol.HelloIntf
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *Model) GetMtimeMapping(arg1 string, arg2 string) (fs.MtimeMapping, error) {
|
||||
fake.getMtimeMappingMutex.Lock()
|
||||
ret, specificReturn := fake.getMtimeMappingReturnsOnCall[len(fake.getMtimeMappingArgsForCall)]
|
||||
@@ -2331,59 +2244,6 @@ func (fake *Model) NeedFolderFilesReturnsOnCall(i int, result1 []db.FileInfoTrun
|
||||
}{result1, result2, result3, result4}
|
||||
}
|
||||
|
||||
func (fake *Model) NumConnections() int {
|
||||
fake.numConnectionsMutex.Lock()
|
||||
ret, specificReturn := fake.numConnectionsReturnsOnCall[len(fake.numConnectionsArgsForCall)]
|
||||
fake.numConnectionsArgsForCall = append(fake.numConnectionsArgsForCall, struct {
|
||||
}{})
|
||||
stub := fake.NumConnectionsStub
|
||||
fakeReturns := fake.numConnectionsReturns
|
||||
fake.recordInvocation("NumConnections", []interface{}{})
|
||||
fake.numConnectionsMutex.Unlock()
|
||||
if stub != nil {
|
||||
return stub()
|
||||
}
|
||||
if specificReturn {
|
||||
return ret.result1
|
||||
}
|
||||
return fakeReturns.result1
|
||||
}
|
||||
|
||||
func (fake *Model) NumConnectionsCallCount() int {
|
||||
fake.numConnectionsMutex.RLock()
|
||||
defer fake.numConnectionsMutex.RUnlock()
|
||||
return len(fake.numConnectionsArgsForCall)
|
||||
}
|
||||
|
||||
func (fake *Model) NumConnectionsCalls(stub func() int) {
|
||||
fake.numConnectionsMutex.Lock()
|
||||
defer fake.numConnectionsMutex.Unlock()
|
||||
fake.NumConnectionsStub = stub
|
||||
}
|
||||
|
||||
func (fake *Model) NumConnectionsReturns(result1 int) {
|
||||
fake.numConnectionsMutex.Lock()
|
||||
defer fake.numConnectionsMutex.Unlock()
|
||||
fake.NumConnectionsStub = nil
|
||||
fake.numConnectionsReturns = struct {
|
||||
result1 int
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *Model) NumConnectionsReturnsOnCall(i int, result1 int) {
|
||||
fake.numConnectionsMutex.Lock()
|
||||
defer fake.numConnectionsMutex.Unlock()
|
||||
fake.NumConnectionsStub = nil
|
||||
if fake.numConnectionsReturnsOnCall == nil {
|
||||
fake.numConnectionsReturnsOnCall = make(map[int]struct {
|
||||
result1 int
|
||||
})
|
||||
}
|
||||
fake.numConnectionsReturnsOnCall[i] = struct {
|
||||
result1 int
|
||||
}{result1}
|
||||
}
|
||||
|
||||
func (fake *Model) OnHello(arg1 protocol.DeviceID, arg2 net.Addr, arg3 protocol.Hello) error {
|
||||
fake.onHelloMutex.Lock()
|
||||
ret, specificReturn := fake.onHelloReturnsOnCall[len(fake.onHelloArgsForCall)]
|
||||
@@ -3419,8 +3279,8 @@ func (fake *Model) Invocations() map[string][][]interface{} {
|
||||
defer fake.clusterConfigMutex.RUnlock()
|
||||
fake.completionMutex.RLock()
|
||||
defer fake.completionMutex.RUnlock()
|
||||
fake.connectionMutex.RLock()
|
||||
defer fake.connectionMutex.RUnlock()
|
||||
fake.connectedToMutex.RLock()
|
||||
defer fake.connectedToMutex.RUnlock()
|
||||
fake.connectionStatsMutex.RLock()
|
||||
defer fake.connectionStatsMutex.RUnlock()
|
||||
fake.currentFolderFileMutex.RLock()
|
||||
@@ -3449,8 +3309,6 @@ func (fake *Model) Invocations() map[string][][]interface{} {
|
||||
defer fake.folderStatisticsMutex.RUnlock()
|
||||
fake.getFolderVersionsMutex.RLock()
|
||||
defer fake.getFolderVersionsMutex.RUnlock()
|
||||
fake.getHelloMutex.RLock()
|
||||
defer fake.getHelloMutex.RUnlock()
|
||||
fake.getMtimeMappingMutex.RLock()
|
||||
defer fake.getMtimeMappingMutex.RUnlock()
|
||||
fake.globalDirectoryTreeMutex.RLock()
|
||||
@@ -3465,8 +3323,6 @@ func (fake *Model) Invocations() map[string][][]interface{} {
|
||||
defer fake.localChangedFolderFilesMutex.RUnlock()
|
||||
fake.needFolderFilesMutex.RLock()
|
||||
defer fake.needFolderFilesMutex.RUnlock()
|
||||
fake.numConnectionsMutex.RLock()
|
||||
defer fake.numConnectionsMutex.RUnlock()
|
||||
fake.onHelloMutex.RLock()
|
||||
defer fake.onHelloMutex.RUnlock()
|
||||
fake.overrideMutex.RLock()
|
||||
|
||||
+377
-198
@@ -37,6 +37,7 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/ignore"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
"github.com/syncthing/syncthing/lib/scanner"
|
||||
"github.com/syncthing/syncthing/lib/semaphore"
|
||||
"github.com/syncthing/syncthing/lib/stats"
|
||||
@@ -108,6 +109,7 @@ type Model interface {
|
||||
DeviceStatistics() (map[protocol.DeviceID]stats.DeviceStatistics, error)
|
||||
FolderStatistics() (map[string]stats.FolderStatistics, error)
|
||||
UsageReportingStats(report *contract.Report, version int, preview bool)
|
||||
ConnectedTo(remoteID protocol.DeviceID) bool
|
||||
|
||||
PendingDevices() (map[protocol.DeviceID]db.ObservedDevice, error)
|
||||
PendingFolders(device protocol.DeviceID) (map[string]db.PendingFolder, error)
|
||||
@@ -124,8 +126,6 @@ type model struct {
|
||||
// constructor parameters
|
||||
cfg config.Wrapper
|
||||
id protocol.DeviceID
|
||||
clientName string
|
||||
clientVersion string
|
||||
db *db.Lowlevel
|
||||
protectedFiles []string
|
||||
evLogger events.Logger
|
||||
@@ -143,6 +143,7 @@ type model struct {
|
||||
fatalChan chan error
|
||||
started chan struct{}
|
||||
keyGen *protocol.KeyGenerator
|
||||
promotionTimer *time.Timer
|
||||
|
||||
// fields protected by fmut
|
||||
fmut sync.RWMutex
|
||||
@@ -158,9 +159,11 @@ type model struct {
|
||||
|
||||
// fields protected by pmut
|
||||
pmut sync.RWMutex
|
||||
conn map[protocol.DeviceID]protocol.Connection
|
||||
connections map[string]protocol.Connection // connection ID -> connection
|
||||
deviceConnIDs map[protocol.DeviceID][]string // device -> connection IDs (invariant: if the key exists, the value is len >= 1, with the primary connection at the start of the slice)
|
||||
promotedConnID map[protocol.DeviceID]string // device -> latest promoted connection ID
|
||||
connRequestLimiters map[protocol.DeviceID]*semaphore.Semaphore
|
||||
closed map[protocol.DeviceID]chan struct{}
|
||||
closed map[string]chan struct{} // connection ID -> closed channel
|
||||
helloMessages map[protocol.DeviceID]protocol.Hello
|
||||
deviceDownloads map[protocol.DeviceID]*deviceDownloadState
|
||||
remoteFolderStates map[protocol.DeviceID]map[string]remoteFolderState // deviceID -> folders
|
||||
@@ -179,13 +182,11 @@ var folderFactories = make(map[config.FolderType]folderFactory)
|
||||
var (
|
||||
errDeviceUnknown = errors.New("unknown device")
|
||||
errDevicePaused = errors.New("device is paused")
|
||||
errDeviceRemoved = errors.New("device has been removed")
|
||||
ErrFolderPaused = errors.New("folder is paused")
|
||||
ErrFolderNotRunning = errors.New("folder is not running")
|
||||
ErrFolderMissing = errors.New("no such folder")
|
||||
errNoVersioner = errors.New("folder has no versioner")
|
||||
// errors about why a connection is closed
|
||||
errReplacingConnection = errors.New("replacing connection")
|
||||
errStopped = errors.New("Syncthing is being stopped")
|
||||
errEncryptionInvConfigLocal = errors.New("can't encrypt outgoing data because local data is encrypted (folder-type receive-encrypted)")
|
||||
errEncryptionInvConfigRemote = errors.New("remote has encrypted data and encrypts that data for us - this is impossible")
|
||||
@@ -203,7 +204,7 @@ var (
|
||||
// NewModel creates and starts a new model. The model starts in read-only mode,
|
||||
// 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, keyGen *protocol.KeyGenerator) Model {
|
||||
func NewModel(cfg config.Wrapper, id protocol.DeviceID, ldb *db.Lowlevel, protectedFiles []string, evLogger events.Logger, keyGen *protocol.KeyGenerator) Model {
|
||||
spec := svcutil.SpecWithDebugLogger(l)
|
||||
m := &model{
|
||||
Supervisor: suture.New("model", spec),
|
||||
@@ -211,8 +212,6 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersio
|
||||
// constructor parameters
|
||||
cfg: cfg,
|
||||
id: id,
|
||||
clientName: clientName,
|
||||
clientVersion: clientVersion,
|
||||
db: ldb,
|
||||
protectedFiles: protectedFiles,
|
||||
evLogger: evLogger,
|
||||
@@ -226,6 +225,7 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersio
|
||||
fatalChan: make(chan error),
|
||||
started: make(chan struct{}),
|
||||
keyGen: keyGen,
|
||||
promotionTimer: time.NewTimer(0),
|
||||
|
||||
// fields protected by fmut
|
||||
fmut: sync.NewRWMutex(),
|
||||
@@ -240,16 +240,19 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersio
|
||||
|
||||
// fields protected by pmut
|
||||
pmut: sync.NewRWMutex(),
|
||||
conn: make(map[protocol.DeviceID]protocol.Connection),
|
||||
connections: make(map[string]protocol.Connection),
|
||||
deviceConnIDs: make(map[protocol.DeviceID][]string),
|
||||
promotedConnID: make(map[protocol.DeviceID]string),
|
||||
connRequestLimiters: make(map[protocol.DeviceID]*semaphore.Semaphore),
|
||||
closed: make(map[protocol.DeviceID]chan struct{}),
|
||||
closed: make(map[string]chan struct{}),
|
||||
helloMessages: make(map[protocol.DeviceID]protocol.Hello),
|
||||
deviceDownloads: make(map[protocol.DeviceID]*deviceDownloadState),
|
||||
remoteFolderStates: make(map[protocol.DeviceID]map[string]remoteFolderState),
|
||||
indexHandlers: newServiceMap[protocol.DeviceID, *indexHandlerRegistry](evLogger),
|
||||
}
|
||||
for devID := range cfg.Devices() {
|
||||
for devID, cfg := range cfg.Devices() {
|
||||
m.deviceStatRefs[devID] = stats.NewDeviceStatisticsReference(m.db, devID)
|
||||
m.setConnRequestLimitersPLocked(cfg)
|
||||
}
|
||||
m.Add(m.folderRunners)
|
||||
m.Add(m.progressEmitter)
|
||||
@@ -272,11 +275,18 @@ func (m *model) serve(ctx context.Context) error {
|
||||
|
||||
close(m.started)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case err := <-m.fatalChan:
|
||||
return svcutil.AsFatalErr(err, svcutil.ExitError)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
l.Debugln(m, "context closed, stopping", ctx.Err())
|
||||
return ctx.Err()
|
||||
case err := <-m.fatalChan:
|
||||
l.Debugln(m, "fatal error, stopping", err)
|
||||
return svcutil.AsFatalErr(err, svcutil.ExitError)
|
||||
case <-m.promotionTimer.C:
|
||||
l.Debugln("promotion timer fired")
|
||||
m.promoteConnections()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,9 +313,9 @@ func (m *model) initFolders(cfg config.Configuration) error {
|
||||
|
||||
func (m *model) closeAllConnectionsAndWait() {
|
||||
m.pmut.RLock()
|
||||
closed := make([]chan struct{}, 0, len(m.conn))
|
||||
for id, conn := range m.conn {
|
||||
closed = append(closed, m.closed[id])
|
||||
closed := make([]chan struct{}, 0, len(m.connections))
|
||||
for connID, conn := range m.connections {
|
||||
closed = append(closed, m.closed[connID])
|
||||
go conn.Close(errStopped)
|
||||
}
|
||||
m.pmut.RUnlock()
|
||||
@@ -635,7 +645,7 @@ func (m *model) UsageReportingStats(report *contract.Report, version int, previe
|
||||
|
||||
// Transport stats
|
||||
m.pmut.RLock()
|
||||
for _, conn := range m.conn {
|
||||
for _, conn := range m.connections {
|
||||
report.TransportStats[conn.Transport()]++
|
||||
}
|
||||
m.pmut.RUnlock()
|
||||
@@ -699,22 +709,28 @@ func (m *model) UsageReportingStats(report *contract.Report, version int, previe
|
||||
}
|
||||
}
|
||||
|
||||
type ConnectionInfo struct {
|
||||
protocol.Statistics
|
||||
type ConnectionStats struct {
|
||||
protocol.Statistics // Total for primary + secondaries
|
||||
|
||||
Connected bool `json:"connected"`
|
||||
Paused bool `json:"paused"`
|
||||
Address string `json:"address"`
|
||||
ClientVersion string `json:"clientVersion"`
|
||||
Type string `json:"type"`
|
||||
IsLocal bool `json:"isLocal"`
|
||||
Crypto string `json:"crypto"`
|
||||
|
||||
Address string `json:"address"` // mirror values from Primary, for compatibility with <1.24.0
|
||||
Type string `json:"type"` // mirror values from Primary, for compatibility with <1.24.0
|
||||
IsLocal bool `json:"isLocal"` // mirror values from Primary, for compatibility with <1.24.0
|
||||
Crypto string `json:"crypto"` // mirror values from Primary, for compatibility with <1.24.0
|
||||
|
||||
Primary ConnectionInfo `json:"primary,omitempty"`
|
||||
Secondary []ConnectionInfo `json:"secondary,omitempty"`
|
||||
}
|
||||
|
||||
// NumConnections returns the current number of active connected devices.
|
||||
func (m *model) NumConnections() int {
|
||||
m.pmut.RLock()
|
||||
defer m.pmut.RUnlock()
|
||||
return len(m.conn)
|
||||
type ConnectionInfo struct {
|
||||
protocol.Statistics
|
||||
Address string `json:"address"`
|
||||
Type string `json:"type"`
|
||||
IsLocal bool `json:"isLocal"`
|
||||
Crypto string `json:"crypto"`
|
||||
}
|
||||
|
||||
// ConnectionStats returns a map with connection statistics for each device.
|
||||
@@ -724,29 +740,59 @@ func (m *model) ConnectionStats() map[string]interface{} {
|
||||
|
||||
res := make(map[string]interface{})
|
||||
devs := m.cfg.Devices()
|
||||
conns := make(map[string]ConnectionInfo, len(devs))
|
||||
conns := make(map[string]ConnectionStats, len(devs))
|
||||
for device, deviceCfg := range devs {
|
||||
if device == m.id {
|
||||
continue
|
||||
}
|
||||
hello := m.helloMessages[device]
|
||||
versionString := hello.ClientVersion
|
||||
if hello.ClientName != "syncthing" {
|
||||
versionString = hello.ClientName + " " + hello.ClientVersion
|
||||
}
|
||||
ci := ConnectionInfo{
|
||||
ClientVersion: strings.TrimSpace(versionString),
|
||||
connIDs, ok := m.deviceConnIDs[device]
|
||||
cs := ConnectionStats{
|
||||
Connected: ok,
|
||||
Paused: deviceCfg.Paused,
|
||||
ClientVersion: strings.TrimSpace(versionString),
|
||||
}
|
||||
if conn, ok := m.conn[device]; ok {
|
||||
ci.Type = conn.Type()
|
||||
ci.IsLocal = conn.IsLocal()
|
||||
ci.Crypto = conn.Crypto()
|
||||
ci.Connected = ok
|
||||
ci.Statistics = conn.Statistics()
|
||||
if addr := conn.RemoteAddr(); addr != nil {
|
||||
ci.Address = addr.String()
|
||||
if ok {
|
||||
conn := m.connections[connIDs[0]]
|
||||
|
||||
cs.Primary.Type = conn.Type()
|
||||
cs.Primary.IsLocal = conn.IsLocal()
|
||||
cs.Primary.Crypto = conn.Crypto()
|
||||
cs.Primary.Statistics = conn.Statistics()
|
||||
cs.Primary.Address = conn.RemoteAddr().String()
|
||||
|
||||
cs.Type = cs.Primary.Type
|
||||
cs.IsLocal = cs.Primary.IsLocal
|
||||
cs.Crypto = cs.Primary.Crypto
|
||||
cs.Address = cs.Primary.Address
|
||||
cs.Statistics = cs.Primary.Statistics
|
||||
|
||||
for _, connID := range connIDs[1:] {
|
||||
conn = m.connections[connID]
|
||||
sec := ConnectionInfo{
|
||||
Statistics: conn.Statistics(),
|
||||
Address: conn.RemoteAddr().String(),
|
||||
Type: conn.Type(),
|
||||
IsLocal: conn.IsLocal(),
|
||||
Crypto: conn.Crypto(),
|
||||
}
|
||||
if sec.At.After(cs.At) {
|
||||
cs.At = sec.At
|
||||
}
|
||||
if sec.StartedAt.Before(cs.StartedAt) {
|
||||
cs.StartedAt = sec.StartedAt
|
||||
}
|
||||
cs.InBytesTotal += sec.InBytesTotal
|
||||
cs.OutBytesTotal += sec.OutBytesTotal
|
||||
cs.Secondary = append(cs.Secondary, sec)
|
||||
}
|
||||
}
|
||||
|
||||
conns[device.String()] = ci
|
||||
conns[device.String()] = cs
|
||||
}
|
||||
|
||||
res["connections"] = conns
|
||||
@@ -1138,17 +1184,16 @@ func (m *model) handleIndex(conn protocol.Connection, folder string, fs []protoc
|
||||
}
|
||||
|
||||
m.pmut.RLock()
|
||||
indexHandler, ok := m.indexHandlers.Get(deviceID)
|
||||
indexHandler, ok := m.getIndexHandlerPRLocked(conn)
|
||||
m.pmut.RUnlock()
|
||||
if !ok {
|
||||
// This should be impossible, as an index handler always exists for an
|
||||
// open connection, and this method can't be called on a closed
|
||||
// connection
|
||||
// This should be impossible, as an index handler is registered when
|
||||
// we send a cluster config, and that is what triggers index
|
||||
// sending.
|
||||
m.evLogger.Log(events.Failure, "index sender does not exist for connection on which indexes were received")
|
||||
l.Debugf("%v for folder (ID %q) sent from device %q: missing index handler", op, folder, deviceID)
|
||||
return fmt.Errorf("index handler missing: %s", folder)
|
||||
return fmt.Errorf("%s: %w", folder, ErrFolderNotRunning)
|
||||
}
|
||||
|
||||
return indexHandler.ReceiveIndex(folder, fs, update, op)
|
||||
}
|
||||
|
||||
@@ -1161,24 +1206,26 @@ type ClusterConfigReceivedEventData struct {
|
||||
}
|
||||
|
||||
func (m *model) ClusterConfig(conn protocol.Connection, cm protocol.ClusterConfig) error {
|
||||
deviceID := conn.DeviceID()
|
||||
|
||||
if cm.Secondary {
|
||||
// No handling of secondary connection ClusterConfigs; they merely
|
||||
// indicate the connection is ready to start.
|
||||
l.Debugf("Skipping secondary ClusterConfig from %v at %s", deviceID.Short(), conn)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check the peer device's announced folders against our own. Emits events
|
||||
// for folders that we don't expect (unknown or not shared).
|
||||
// Also, collect a list of folders we do share, and if he's interested in
|
||||
// temporary indexes, subscribe the connection.
|
||||
|
||||
deviceID := conn.DeviceID()
|
||||
l.Debugf("Handling ClusterConfig from %v", deviceID.Short())
|
||||
|
||||
m.pmut.RLock()
|
||||
indexHandlerRegistry, ok := m.indexHandlers.Get(deviceID)
|
||||
m.pmut.RUnlock()
|
||||
if !ok {
|
||||
panic("bug: ClusterConfig called on closed or nonexistent connection")
|
||||
}
|
||||
l.Debugf("Handling ClusterConfig from %v at %s", deviceID.Short(), conn)
|
||||
indexHandlerRegistry := m.ensureIndexHandler(conn)
|
||||
|
||||
deviceCfg, ok := m.cfg.Device(deviceID)
|
||||
if !ok {
|
||||
l.Debugln("Device disappeared from config while processing cluster-config")
|
||||
l.Debugf("Device %s disappeared from config while processing cluster-config", deviceID.Short())
|
||||
return errDeviceUnknown
|
||||
}
|
||||
|
||||
@@ -1198,11 +1245,11 @@ func (m *model) ClusterConfig(conn protocol.Connection, cm protocol.ClusterConfi
|
||||
}
|
||||
}
|
||||
if info.remote.ID == protocol.EmptyDeviceID {
|
||||
l.Infof("Device %v sent cluster-config without the device info for the remote on folder %v", deviceID, folder.Description())
|
||||
l.Infof("Device %v sent cluster-config without the device info for the remote on folder %v", deviceID.Short(), folder.Description())
|
||||
return errMissingRemoteInClusterConfig
|
||||
}
|
||||
if info.local.ID == protocol.EmptyDeviceID {
|
||||
l.Infof("Device %v sent cluster-config without the device info for us locally on folder %v", deviceID, folder.Description())
|
||||
l.Infof("Device %v sent cluster-config without the device info for us locally on folder %v", deviceID.Short(), folder.Description())
|
||||
return errMissingLocalInClusterConfig
|
||||
}
|
||||
ccDeviceInfos[folder.ID] = info
|
||||
@@ -1260,12 +1307,16 @@ func (m *model) ClusterConfig(conn protocol.Connection, cm protocol.ClusterConfi
|
||||
})
|
||||
|
||||
if len(tempIndexFolders) > 0 {
|
||||
var connOK bool
|
||||
var conn protocol.Connection
|
||||
m.pmut.RLock()
|
||||
conn, ok := m.conn[deviceID]
|
||||
if connIDs, connIDOK := m.deviceConnIDs[deviceID]; connIDOK {
|
||||
conn, connOK = m.connections[connIDs[0]]
|
||||
}
|
||||
m.pmut.RUnlock()
|
||||
// In case we've got ClusterConfig, and the connection disappeared
|
||||
// from infront of our nose.
|
||||
if ok {
|
||||
if connOK {
|
||||
m.progressEmitter.temporaryIndexSubscribe(conn, tempIndexFolders)
|
||||
}
|
||||
}
|
||||
@@ -1291,6 +1342,57 @@ func (m *model) ClusterConfig(conn protocol.Connection, cm protocol.ClusterConfi
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *model) ensureIndexHandler(conn protocol.Connection) *indexHandlerRegistry {
|
||||
deviceID := conn.DeviceID()
|
||||
connID := conn.ConnectionID()
|
||||
|
||||
m.pmut.Lock()
|
||||
defer m.pmut.Unlock()
|
||||
|
||||
indexHandlerRegistry, ok := m.indexHandlers.Get(deviceID)
|
||||
if ok && indexHandlerRegistry.conn.ConnectionID() == connID {
|
||||
// This is an existing and proper index handler for this connection.
|
||||
return indexHandlerRegistry
|
||||
}
|
||||
|
||||
if ok {
|
||||
// A handler exists, but it's for another connection than the one we
|
||||
// now got a ClusterConfig on. This should be unusual as it means
|
||||
// the other side has decided to start using a new primary
|
||||
// connection but we haven't seen it close yet. Ideally it will
|
||||
// close shortly by itself...
|
||||
l.Infof("Abandoning old index handler for %s (%s) in favour of %s", deviceID.Short(), indexHandlerRegistry.conn.ConnectionID(), connID)
|
||||
m.indexHandlers.RemoveAndWait(deviceID, 0)
|
||||
}
|
||||
|
||||
// Create a new index handler for this device.
|
||||
indexHandlerRegistry = newIndexHandlerRegistry(conn, m.deviceDownloads[deviceID], m.evLogger)
|
||||
for id, fcfg := range m.folderCfgs {
|
||||
l.Debugln("Registering folder", id, "for", deviceID.Short())
|
||||
runner, _ := m.folderRunners.Get(id)
|
||||
indexHandlerRegistry.RegisterFolderState(fcfg, m.folderFiles[id], runner)
|
||||
}
|
||||
m.indexHandlers.Add(deviceID, indexHandlerRegistry)
|
||||
|
||||
return indexHandlerRegistry
|
||||
}
|
||||
|
||||
func (m *model) getIndexHandlerPRLocked(conn protocol.Connection) (*indexHandlerRegistry, bool) {
|
||||
// Reads from index handlers, which requires pmut to be read locked
|
||||
|
||||
deviceID := conn.DeviceID()
|
||||
connID := conn.ConnectionID()
|
||||
|
||||
indexHandlerRegistry, ok := m.indexHandlers.Get(deviceID)
|
||||
if ok && indexHandlerRegistry.conn.ConnectionID() == connID {
|
||||
// This is an existing and proper index handler for this connection.
|
||||
return indexHandlerRegistry, true
|
||||
}
|
||||
|
||||
// There is no index handler, or it's not registered for this connection.
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (m *model) ccHandleFolders(folders []protocol.Folder, deviceCfg config.DeviceConfiguration, ccDeviceInfos map[string]*clusterConfigDeviceInfo, indexHandlers *indexHandlerRegistry) ([]string, map[string]remoteFolderState, error) {
|
||||
var folderDevice config.FolderDeviceConfiguration
|
||||
tempIndexFolders := make([]string, 0, len(folders))
|
||||
@@ -1539,8 +1641,8 @@ func (m *model) sendClusterConfig(ids []protocol.DeviceID) {
|
||||
ccConns := make([]protocol.Connection, 0, len(ids))
|
||||
m.pmut.RLock()
|
||||
for _, id := range ids {
|
||||
if conn, ok := m.conn[id]; ok {
|
||||
ccConns = append(ccConns, conn)
|
||||
if connIDs, ok := m.deviceConnIDs[id]; ok {
|
||||
ccConns = append(ccConns, m.connections[connIDs[0]])
|
||||
}
|
||||
}
|
||||
m.pmut.RUnlock()
|
||||
@@ -1777,33 +1879,62 @@ func (m *model) introduceDevice(device protocol.Device, introducerCfg config.Dev
|
||||
|
||||
// Closed is called when a connection has been closed
|
||||
func (m *model) Closed(conn protocol.Connection, err error) {
|
||||
device := conn.DeviceID()
|
||||
connID := conn.ConnectionID()
|
||||
deviceID := conn.DeviceID()
|
||||
|
||||
m.pmut.Lock()
|
||||
conn, ok := m.conn[device]
|
||||
conn, ok := m.connections[connID]
|
||||
if !ok {
|
||||
m.pmut.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
delete(m.conn, device)
|
||||
delete(m.connRequestLimiters, device)
|
||||
delete(m.helloMessages, device)
|
||||
delete(m.deviceDownloads, device)
|
||||
delete(m.remoteFolderStates, device)
|
||||
closed := m.closed[device]
|
||||
delete(m.closed, device)
|
||||
wait := m.indexHandlers.RemoveAndWaitChan(device, 0)
|
||||
closed := m.closed[connID]
|
||||
delete(m.closed, connID)
|
||||
delete(m.connections, connID)
|
||||
|
||||
removedIsPrimary := m.promotedConnID[deviceID] == connID
|
||||
remainingConns := without(m.deviceConnIDs[deviceID], connID)
|
||||
var wait <-chan error
|
||||
if removedIsPrimary {
|
||||
m.progressEmitter.temporaryIndexUnsubscribe(conn)
|
||||
if idxh, ok := m.indexHandlers.Get(deviceID); ok && idxh.conn.ConnectionID() == connID {
|
||||
wait = m.indexHandlers.RemoveAndWaitChan(deviceID, 0)
|
||||
}
|
||||
m.scheduleConnectionPromotion()
|
||||
}
|
||||
if len(remainingConns) == 0 {
|
||||
// All device connections closed
|
||||
delete(m.deviceConnIDs, deviceID)
|
||||
delete(m.promotedConnID, deviceID)
|
||||
delete(m.connRequestLimiters, deviceID)
|
||||
delete(m.helloMessages, deviceID)
|
||||
delete(m.remoteFolderStates, deviceID)
|
||||
delete(m.deviceDownloads, deviceID)
|
||||
} else {
|
||||
// Some connections remain
|
||||
m.deviceConnIDs[deviceID] = remainingConns
|
||||
}
|
||||
|
||||
m.pmut.Unlock()
|
||||
<-wait
|
||||
if wait != nil {
|
||||
<-wait
|
||||
}
|
||||
|
||||
m.progressEmitter.temporaryIndexUnsubscribe(conn)
|
||||
m.deviceDidClose(device, time.Since(conn.EstablishedAt()))
|
||||
m.fmut.RLock()
|
||||
m.deviceDidCloseFRLocked(deviceID, time.Since(conn.EstablishedAt()))
|
||||
m.fmut.RUnlock()
|
||||
|
||||
l.Infof("Connection to %s at %s closed: %v", device, conn, err)
|
||||
m.evLogger.Log(events.DeviceDisconnected, map[string]string{
|
||||
"id": device.String(),
|
||||
"error": err.Error(),
|
||||
})
|
||||
k := map[bool]string{false: "secondary", true: "primary"}[removedIsPrimary]
|
||||
l.Infof("Lost %s connection to %s at %s: %v (%d remain)", k, deviceID.Short(), conn, err, len(remainingConns))
|
||||
|
||||
if len(remainingConns) == 0 {
|
||||
l.Infof("Connection to %s at %s closed: %v", deviceID.Short(), conn, err)
|
||||
m.evLogger.Log(events.DeviceDisconnected, map[string]string{
|
||||
"id": deviceID.String(),
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
close(closed)
|
||||
}
|
||||
|
||||
@@ -1852,36 +1983,36 @@ func (m *model) Request(conn protocol.Connection, folder, name string, _, size i
|
||||
if !ok {
|
||||
// The folder might be already unpaused in the config, but not yet
|
||||
// in the model.
|
||||
l.Debugf("Request from %s for file %s in unstarted folder %q", deviceID, name, folder)
|
||||
l.Debugf("Request from %s for file %s in unstarted folder %q", deviceID.Short(), name, folder)
|
||||
return nil, protocol.ErrGeneric
|
||||
}
|
||||
|
||||
if !folderCfg.SharedWith(deviceID) {
|
||||
l.Warnf("Request from %s for file %s in unshared folder %q", deviceID, name, folder)
|
||||
l.Warnf("Request from %s for file %s in unshared folder %q", deviceID.Short(), name, folder)
|
||||
return nil, protocol.ErrGeneric
|
||||
}
|
||||
if folderCfg.Paused {
|
||||
l.Debugf("Request from %s for file %s in paused folder %q", deviceID, name, folder)
|
||||
l.Debugf("Request from %s for file %s in paused folder %q", deviceID.Short(), name, folder)
|
||||
return nil, protocol.ErrGeneric
|
||||
}
|
||||
|
||||
// Make sure the path is valid and in canonical form
|
||||
if name, err = fs.Canonicalize(name); err != nil {
|
||||
l.Debugf("Request from %s in folder %q for invalid filename %s", deviceID, folder, name)
|
||||
l.Debugf("Request from %s in folder %q for invalid filename %s", deviceID.Short(), folder, name)
|
||||
return nil, protocol.ErrGeneric
|
||||
}
|
||||
|
||||
if deviceID != protocol.LocalDeviceID {
|
||||
l.Debugf("%v REQ(in): %s: %q / %q o=%d s=%d t=%v", m, deviceID, folder, name, offset, size, fromTemporary)
|
||||
l.Debugf("%v REQ(in): %s: %q / %q o=%d s=%d t=%v", m, deviceID.Short(), folder, name, offset, size, fromTemporary)
|
||||
}
|
||||
|
||||
if fs.IsInternal(name) {
|
||||
l.Debugf("%v REQ(in) for internal file: %s: %q / %q o=%d s=%d", m, deviceID, folder, name, offset, size)
|
||||
l.Debugf("%v REQ(in) for internal file: %s: %q / %q o=%d s=%d", m, deviceID.Short(), folder, name, offset, size)
|
||||
return nil, protocol.ErrInvalid
|
||||
}
|
||||
|
||||
if folderIgnores.Match(name).IsIgnored() {
|
||||
l.Debugf("%v REQ(in) for ignored file: %s: %q / %q o=%d s=%d", m, deviceID, folder, name, offset, size)
|
||||
l.Debugf("%v REQ(in) for ignored file: %s: %q / %q o=%d s=%d", m, deviceID.Short(), folder, name, offset, size)
|
||||
return nil, protocol.ErrInvalid
|
||||
}
|
||||
|
||||
@@ -1908,7 +2039,7 @@ func (m *model) Request(conn protocol.Connection, folder, name string, _, size i
|
||||
folderFs := folderCfg.Filesystem(nil)
|
||||
|
||||
if err := osutil.TraversesSymlink(folderFs, filepath.Dir(name)); err != nil {
|
||||
l.Debugf("%v REQ(in) traversal check: %s - %s: %q / %q o=%d s=%d", m, err, deviceID, folder, name, offset, size)
|
||||
l.Debugf("%v REQ(in) traversal check: %s - %s: %q / %q o=%d s=%d", m, err, deviceID.Short(), folder, name, offset, size)
|
||||
return nil, protocol.ErrNoSuchFile
|
||||
}
|
||||
|
||||
@@ -1920,7 +2051,7 @@ func (m *model) Request(conn protocol.Connection, folder, name string, _, size i
|
||||
if info, err := folderFs.Lstat(tempFn); err != nil || !info.IsRegular() {
|
||||
// Reject reads for anything that doesn't exist or is something
|
||||
// other than a regular file.
|
||||
l.Debugf("%v REQ(in) failed stating temp file (%v): %s: %q / %q o=%d s=%d", m, err, deviceID, folder, name, offset, size)
|
||||
l.Debugf("%v REQ(in) failed stating temp file (%v): %s: %q / %q o=%d s=%d", m, err, deviceID.Short(), folder, name, offset, size)
|
||||
return nil, protocol.ErrNoSuchFile
|
||||
}
|
||||
_, err := readOffsetIntoBuf(folderFs, tempFn, offset, res.data)
|
||||
@@ -1934,13 +2065,13 @@ func (m *model) Request(conn protocol.Connection, folder, name string, _, size i
|
||||
if info, err := folderFs.Lstat(name); err != nil || !info.IsRegular() {
|
||||
// Reject reads for anything that doesn't exist or is something
|
||||
// other than a regular file.
|
||||
l.Debugf("%v REQ(in) failed stating file (%v): %s: %q / %q o=%d s=%d", m, err, deviceID, folder, name, offset, size)
|
||||
l.Debugf("%v REQ(in) failed stating file (%v): %s: %q / %q o=%d s=%d", m, err, deviceID.Short(), folder, name, offset, size)
|
||||
return nil, protocol.ErrNoSuchFile
|
||||
}
|
||||
|
||||
n, err := readOffsetIntoBuf(folderFs, name, offset, res.data)
|
||||
if fs.IsNotExist(err) {
|
||||
l.Debugf("%v REQ(in) file doesn't exist: %s: %q / %q o=%d s=%d", m, deviceID, folder, name, offset, size)
|
||||
l.Debugf("%v REQ(in) file doesn't exist: %s: %q / %q o=%d s=%d", m, deviceID.Short(), folder, name, offset, size)
|
||||
return nil, protocol.ErrNoSuchFile
|
||||
} else if err == io.EOF {
|
||||
// Read beyond end of file. This might indicate a problem, or it
|
||||
@@ -1949,13 +2080,13 @@ func (m *model) Request(conn protocol.Connection, folder, name string, _, size i
|
||||
// next step take care of it, by only hashing the part we actually
|
||||
// managed to read.
|
||||
} else if err != nil {
|
||||
l.Debugf("%v REQ(in) failed reading file (%v): %s: %q / %q o=%d s=%d", m, err, deviceID, folder, name, offset, size)
|
||||
l.Debugf("%v REQ(in) failed reading file (%v): %s: %q / %q o=%d s=%d", m, err, deviceID.Short(), folder, name, offset, size)
|
||||
return nil, protocol.ErrGeneric
|
||||
}
|
||||
|
||||
if folderCfg.Type != config.FolderTypeReceiveEncrypted && len(hash) > 0 && !scanner.Validate(res.data[:n], hash, weakHash) {
|
||||
m.recheckFile(deviceID, folder, name, offset, hash, weakHash)
|
||||
l.Debugf("%v REQ(in) failed validating data: %s: %q / %q o=%d s=%d", m, deviceID, folder, name, offset, size)
|
||||
l.Debugf("%v REQ(in) failed validating data: %s: %q / %q o=%d s=%d", m, deviceID.Short(), folder, name, offset, size)
|
||||
return nil, protocol.ErrNoSuchFile
|
||||
}
|
||||
|
||||
@@ -2074,15 +2205,12 @@ func (m *model) GetMtimeMapping(folder string, file string) (fs.MtimeMapping, er
|
||||
return fs.GetMtimeMapping(fcfg.Filesystem(ffs), file)
|
||||
}
|
||||
|
||||
// Connection returns the current connection for device, and a boolean whether a connection was found.
|
||||
func (m *model) Connection(deviceID protocol.DeviceID) (protocol.Connection, bool) {
|
||||
// Connection returns if we are connected to the given device.
|
||||
func (m *model) ConnectedTo(deviceID protocol.DeviceID) bool {
|
||||
m.pmut.RLock()
|
||||
cn, ok := m.conn[deviceID]
|
||||
_, ok := m.deviceConnIDs[deviceID]
|
||||
m.pmut.RUnlock()
|
||||
if ok {
|
||||
m.deviceWasSeen(deviceID)
|
||||
}
|
||||
return cn, ok
|
||||
return ok
|
||||
}
|
||||
|
||||
// LoadIgnores loads or refreshes the ignore patterns from disk, if the
|
||||
@@ -2200,74 +2328,29 @@ func (m *model) OnHello(remoteID protocol.DeviceID, addr net.Addr, hello protoco
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHello is called when we are about to connect to some remote device.
|
||||
func (m *model) GetHello(id protocol.DeviceID) protocol.HelloIntf {
|
||||
name := ""
|
||||
if _, ok := m.cfg.Device(id); ok {
|
||||
// Set our name (from the config of our device ID) only if we already know about the other side device ID.
|
||||
if myCfg, ok := m.cfg.Device(m.id); ok {
|
||||
name = myCfg.Name
|
||||
}
|
||||
}
|
||||
return &protocol.Hello{
|
||||
DeviceName: name,
|
||||
ClientName: m.clientName,
|
||||
ClientVersion: m.clientVersion,
|
||||
}
|
||||
}
|
||||
|
||||
// AddConnection adds a new peer connection to the model. An initial index will
|
||||
// be sent to the connected peer, thereafter index updates whenever the local
|
||||
// folder changes.
|
||||
func (m *model) AddConnection(conn protocol.Connection, hello protocol.Hello) {
|
||||
deviceID := conn.DeviceID()
|
||||
device, ok := m.cfg.Device(deviceID)
|
||||
deviceCfg, ok := m.cfg.Device(deviceID)
|
||||
if !ok {
|
||||
l.Infoln("Trying to add connection to unknown device")
|
||||
return
|
||||
}
|
||||
|
||||
// The slightly unusual locking sequence here is because we must acquire
|
||||
// fmut before pmut. (The locks can be *released* in any order.)
|
||||
m.fmut.RLock()
|
||||
m.pmut.Lock()
|
||||
if oldConn, ok := m.conn[deviceID]; ok {
|
||||
l.Infoln("Replacing old connection", oldConn, "with", conn, "for", deviceID)
|
||||
// There is an existing connection to this device that we are
|
||||
// replacing. We must close the existing connection and wait for the
|
||||
// close to complete before adding the new connection. We do the
|
||||
// actual close without holding pmut as the connection will call
|
||||
// back into Closed() for the cleanup.
|
||||
closed := m.closed[deviceID]
|
||||
m.fmut.RUnlock()
|
||||
m.pmut.Unlock()
|
||||
oldConn.Close(errReplacingConnection)
|
||||
<-closed
|
||||
// Again, lock fmut before pmut.
|
||||
m.fmut.RLock()
|
||||
m.pmut.Lock()
|
||||
}
|
||||
|
||||
m.conn[deviceID] = conn
|
||||
connID := conn.ConnectionID()
|
||||
closed := make(chan struct{})
|
||||
m.closed[deviceID] = closed
|
||||
m.deviceDownloads[deviceID] = newDeviceDownloadState()
|
||||
indexRegistry := newIndexHandlerRegistry(conn, m.deviceDownloads[deviceID], m.evLogger)
|
||||
for id, fcfg := range m.folderCfgs {
|
||||
runner, _ := m.folderRunners.Get(id)
|
||||
indexRegistry.RegisterFolderState(fcfg, m.folderFiles[id], runner)
|
||||
}
|
||||
m.indexHandlers.Add(deviceID, indexRegistry)
|
||||
m.fmut.RUnlock()
|
||||
// 0: default, <0: no limiting
|
||||
switch {
|
||||
case device.MaxRequestKiB > 0:
|
||||
m.connRequestLimiters[deviceID] = semaphore.New(1024 * device.MaxRequestKiB)
|
||||
case device.MaxRequestKiB == 0:
|
||||
m.connRequestLimiters[deviceID] = semaphore.New(1024 * defaultPullerPendingKiB)
|
||||
}
|
||||
|
||||
m.pmut.Lock()
|
||||
|
||||
m.connections[connID] = conn
|
||||
m.closed[connID] = closed
|
||||
m.helloMessages[deviceID] = hello
|
||||
m.deviceConnIDs[deviceID] = append(m.deviceConnIDs[deviceID], connID)
|
||||
if m.deviceDownloads[deviceID] == nil {
|
||||
m.deviceDownloads[deviceID] = newDeviceDownloadState()
|
||||
}
|
||||
|
||||
event := map[string]string{
|
||||
"id": deviceID.String(),
|
||||
@@ -2284,17 +2367,15 @@ func (m *model) AddConnection(conn protocol.Connection, hello protocol.Hello) {
|
||||
|
||||
m.evLogger.Log(events.DeviceConnected, event)
|
||||
|
||||
l.Infof(`Device %s client is "%s %s" named "%s" at %s`, deviceID, hello.ClientName, hello.ClientVersion, hello.DeviceName, conn)
|
||||
if len(m.deviceConnIDs[deviceID]) == 1 {
|
||||
l.Infof(`Device %s client is "%s %s" named "%s" at %s`, deviceID.Short(), hello.ClientName, hello.ClientVersion, hello.DeviceName, conn)
|
||||
} else {
|
||||
l.Infof(`Additional connection (+%d) for device %s at %s`, len(m.deviceConnIDs[deviceID])-1, deviceID.Short(), conn)
|
||||
}
|
||||
|
||||
conn.Start()
|
||||
m.pmut.Unlock()
|
||||
|
||||
// Acquires fmut, so has to be done outside of pmut.
|
||||
cm, passwords := m.generateClusterConfig(deviceID)
|
||||
conn.SetFolderPasswords(passwords)
|
||||
conn.ClusterConfig(cm)
|
||||
|
||||
if (device.Name == "" || m.cfg.Options().OverwriteRemoteDevNames) && hello.DeviceName != "" {
|
||||
if (deviceCfg.Name == "" || m.cfg.Options().OverwriteRemoteDevNames) && hello.DeviceName != "" {
|
||||
m.cfg.Modify(func(cfg *config.Configuration) {
|
||||
for i := range cfg.Devices {
|
||||
if cfg.Devices[i].DeviceID == deviceID {
|
||||
@@ -2308,26 +2389,78 @@ func (m *model) AddConnection(conn protocol.Connection, hello protocol.Hello) {
|
||||
}
|
||||
|
||||
m.deviceWasSeen(deviceID)
|
||||
m.scheduleConnectionPromotion()
|
||||
}
|
||||
|
||||
func (m *model) scheduleConnectionPromotion() {
|
||||
// Keeps deferring to prevent multiple executions in quick succession,
|
||||
// e.g. if multiple connections to a single device are closed.
|
||||
m.promotionTimer.Reset(time.Second)
|
||||
}
|
||||
|
||||
// promoteConnections checks for devices that have connections, but where
|
||||
// the primary connection hasn't started index handlers etc. yet, and
|
||||
// promotes the primary connection to be the index handling one. This should
|
||||
// be called after adding new connections, and after closing a primary
|
||||
// device connection.
|
||||
func (m *model) promoteConnections() {
|
||||
m.fmut.RLock() // for generateClusterConfigFRLocked
|
||||
defer m.fmut.RUnlock()
|
||||
|
||||
m.pmut.Lock() // for most other things
|
||||
defer m.pmut.Unlock()
|
||||
|
||||
for deviceID, connIDs := range m.deviceConnIDs {
|
||||
cm, passwords := m.generateClusterConfigFRLocked(deviceID)
|
||||
if m.promotedConnID[deviceID] != connIDs[0] {
|
||||
// The previously promoted connection is not the current
|
||||
// primary; we should promote the primary connection to be the
|
||||
// index handling one. We do this by sending a ClusterConfig on
|
||||
// it, which will cause the other side to start sending us index
|
||||
// messages there. (On our side, we manage index handlers based
|
||||
// on where we get ClusterConfigs from the peer.)
|
||||
conn := m.connections[connIDs[0]]
|
||||
l.Debugf("Promoting connection to %s at %s", deviceID.Short(), conn)
|
||||
if conn.Statistics().StartedAt.IsZero() {
|
||||
conn.SetFolderPasswords(passwords)
|
||||
conn.Start()
|
||||
}
|
||||
conn.ClusterConfig(cm)
|
||||
m.promotedConnID[deviceID] = connIDs[0]
|
||||
}
|
||||
|
||||
// Make sure any other new connections also get started, and that
|
||||
// they get a secondary-marked ClusterConfig.
|
||||
for _, connID := range connIDs[1:] {
|
||||
conn := m.connections[connID]
|
||||
if conn.Statistics().StartedAt.IsZero() {
|
||||
conn.SetFolderPasswords(passwords)
|
||||
conn.Start()
|
||||
conn.ClusterConfig(protocol.ClusterConfig{Secondary: true})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) DownloadProgress(conn protocol.Connection, folder string, updates []protocol.FileDownloadProgressUpdate) error {
|
||||
deviceID := conn.DeviceID()
|
||||
|
||||
m.fmut.RLock()
|
||||
cfg, ok := m.folderCfgs[folder]
|
||||
m.fmut.RUnlock()
|
||||
|
||||
device := conn.DeviceID()
|
||||
if !ok || cfg.DisableTempIndexes || !cfg.SharedWith(device) {
|
||||
if !ok || cfg.DisableTempIndexes || !cfg.SharedWith(deviceID) {
|
||||
return nil
|
||||
}
|
||||
|
||||
m.pmut.RLock()
|
||||
downloads := m.deviceDownloads[device]
|
||||
downloads := m.deviceDownloads[deviceID]
|
||||
m.pmut.RUnlock()
|
||||
downloads.Update(folder, updates)
|
||||
state := downloads.GetBlockCounts(folder)
|
||||
|
||||
m.evLogger.Log(events.RemoteDownloadProgress, map[string]interface{}{
|
||||
"device": device.String(),
|
||||
"device": deviceID.String(),
|
||||
"folder": folder,
|
||||
"state": state,
|
||||
})
|
||||
@@ -2344,27 +2477,47 @@ func (m *model) deviceWasSeen(deviceID protocol.DeviceID) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) deviceDidClose(deviceID protocol.DeviceID, duration time.Duration) {
|
||||
m.fmut.RLock()
|
||||
sr, ok := m.deviceStatRefs[deviceID]
|
||||
m.fmut.RUnlock()
|
||||
if ok {
|
||||
func (m *model) deviceDidCloseFRLocked(deviceID protocol.DeviceID, duration time.Duration) {
|
||||
if sr, ok := m.deviceStatRefs[deviceID]; ok {
|
||||
_ = sr.LastConnectionDuration(duration)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) requestGlobal(ctx context.Context, deviceID protocol.DeviceID, folder, name string, blockNo int, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
|
||||
m.pmut.RLock()
|
||||
nc, ok := m.conn[deviceID]
|
||||
m.pmut.RUnlock()
|
||||
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("requestGlobal: no such device: %s", deviceID)
|
||||
conn, connOK := m.requestConnectionForDevice(deviceID)
|
||||
if !connOK {
|
||||
return nil, fmt.Errorf("requestGlobal: no connection to device: %s", deviceID.Short())
|
||||
}
|
||||
|
||||
l.Debugf("%v REQ(out): %s: %q / %q b=%d o=%d s=%d h=%x wh=%x ft=%t", m, deviceID, folder, name, blockNo, offset, size, hash, weakHash, fromTemporary)
|
||||
l.Debugf("%v REQ(out): %s (%s): %q / %q b=%d o=%d s=%d h=%x wh=%x ft=%t", m, deviceID.Short(), conn, folder, name, blockNo, offset, size, hash, weakHash, fromTemporary)
|
||||
return conn.Request(ctx, folder, name, blockNo, offset, size, hash, weakHash, fromTemporary)
|
||||
}
|
||||
|
||||
return nc.Request(ctx, folder, name, blockNo, offset, size, hash, weakHash, fromTemporary)
|
||||
// requestConnectionForDevice returns a connection to the given device, to
|
||||
// be used for sending a request. If there is only one device connection,
|
||||
// this is the one to use. If there are multiple then we avoid the first
|
||||
// ("primary") connection, which is dedicated to index data, and pick a
|
||||
// random one of the others.
|
||||
func (m *model) requestConnectionForDevice(deviceID protocol.DeviceID) (protocol.Connection, bool) {
|
||||
m.pmut.RLock()
|
||||
defer m.pmut.RUnlock()
|
||||
|
||||
connIDs, ok := m.deviceConnIDs[deviceID]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// If there is an entry in deviceConns, it always contains at least one
|
||||
// connection.
|
||||
connID := connIDs[0]
|
||||
if len(connIDs) > 1 {
|
||||
// Pick a random connection of the non-primary ones
|
||||
idx := rand.Intn(len(connIDs)-1) + 1
|
||||
connID = connIDs[idx]
|
||||
}
|
||||
|
||||
conn, connOK := m.connections[connID]
|
||||
return conn, connOK
|
||||
}
|
||||
|
||||
func (m *model) ScanFolders() map[string]error {
|
||||
@@ -2455,11 +2608,13 @@ func (m *model) numHashers(folder string) int {
|
||||
// generateClusterConfig returns a ClusterConfigMessage that is correct and the
|
||||
// set of folder passwords for the given peer device
|
||||
func (m *model) generateClusterConfig(device protocol.DeviceID) (protocol.ClusterConfig, map[string]string) {
|
||||
var message protocol.ClusterConfig
|
||||
|
||||
m.fmut.RLock()
|
||||
defer m.fmut.RUnlock()
|
||||
return m.generateClusterConfigFRLocked(device)
|
||||
}
|
||||
|
||||
func (m *model) generateClusterConfigFRLocked(device protocol.DeviceID) (protocol.ClusterConfig, map[string]string) {
|
||||
var message protocol.ClusterConfig
|
||||
folders := m.cfg.FolderList()
|
||||
passwords := make(map[string]string, len(folders))
|
||||
for _, folderCfg := range folders {
|
||||
@@ -2771,7 +2926,7 @@ func (m *model) availabilityInSnapshotPRlocked(cfg config.FolderConfiguration, s
|
||||
if state := m.remoteFolderStates[device][cfg.ID]; state != remoteFolderValid {
|
||||
continue
|
||||
}
|
||||
_, ok := m.conn[device]
|
||||
_, ok := m.deviceConnIDs[device]
|
||||
if ok {
|
||||
availabilities = append(availabilities, Availability{ID: device, FromTemporary: false})
|
||||
}
|
||||
@@ -2904,13 +3059,6 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Removing a device. We actually don't need to do anything.
|
||||
// Because folder config has changed (since the device lists do not match)
|
||||
// Folders for that had device got "restarted", which involves killing
|
||||
// connections to all devices that we were sharing the folder with.
|
||||
// At some point model.Close() will get called for that device which will
|
||||
// clean residue device state that is not part of any folder.
|
||||
|
||||
// Pausing a device, unpausing is handled by the connection service.
|
||||
fromDevices := from.DeviceMap()
|
||||
toDevices := to.DeviceMap()
|
||||
@@ -2941,7 +3089,14 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
|
||||
l.Infoln("Resuming", deviceID)
|
||||
m.evLogger.Log(events.DeviceResumed, map[string]string{"device": deviceID.String()})
|
||||
}
|
||||
|
||||
if toCfg.MaxRequestKiB != fromCfg.MaxRequestKiB {
|
||||
m.pmut.Lock()
|
||||
m.setConnRequestLimitersPLocked(toCfg)
|
||||
m.pmut.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up after removed devices
|
||||
removedDevices := make([]protocol.DeviceID, 0, len(fromDevices))
|
||||
m.fmut.Lock()
|
||||
@@ -2955,14 +3110,18 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
|
||||
m.pmut.RLock()
|
||||
for _, id := range closeDevices {
|
||||
delete(clusterConfigDevices, id)
|
||||
if conn, ok := m.conn[id]; ok {
|
||||
go conn.Close(errDevicePaused)
|
||||
if conns, ok := m.deviceConnIDs[id]; ok {
|
||||
for _, connID := range conns {
|
||||
go m.connections[connID].Close(errDevicePaused)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, id := range removedDevices {
|
||||
delete(clusterConfigDevices, id)
|
||||
if conn, ok := m.conn[id]; ok {
|
||||
go conn.Close(errDeviceRemoved)
|
||||
if conns, ok := m.deviceConnIDs[id]; ok {
|
||||
for _, connID := range conns {
|
||||
go m.connections[connID].Close(errDevicePaused)
|
||||
}
|
||||
}
|
||||
}
|
||||
m.pmut.RUnlock()
|
||||
@@ -2986,6 +3145,17 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *model) setConnRequestLimitersPLocked(cfg config.DeviceConfiguration) {
|
||||
// Touches connRequestLimiters which is protected by pmut.
|
||||
// 0: default, <0: no limiting
|
||||
switch {
|
||||
case cfg.MaxRequestKiB > 0:
|
||||
m.connRequestLimiters[cfg.DeviceID] = semaphore.New(1024 * cfg.MaxRequestKiB)
|
||||
case cfg.MaxRequestKiB == 0:
|
||||
m.connRequestLimiters[cfg.DeviceID] = semaphore.New(1024 * defaultPullerPendingKiB)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) cleanPending(existingDevices map[protocol.DeviceID]config.DeviceConfiguration, existingFolders map[string]config.FolderConfiguration, ignoredDevices deviceIDSet, removedFolders map[string]struct{}) {
|
||||
var removedPendingFolders []map[string]string
|
||||
pendingFolders, err := m.db.PendingFolders()
|
||||
@@ -3332,3 +3502,12 @@ type redactedError struct {
|
||||
error
|
||||
redacted error
|
||||
}
|
||||
|
||||
func without[E comparable, S ~[]E](s S, e E) S {
|
||||
for i, x := range s {
|
||||
if x == e {
|
||||
return append(s[:i], s[i+1:]...)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
+14
-12
@@ -270,7 +270,7 @@ func TestDeviceRename(t *testing.T) {
|
||||
cfg, cfgCancel := newConfigWrapper(rawCfg)
|
||||
defer cfgCancel()
|
||||
|
||||
m := newModel(t, cfg, myID, "syncthing", "dev", nil)
|
||||
m := newModel(t, cfg, myID, nil)
|
||||
|
||||
if cfg.Devices()[device1].Name != "" {
|
||||
t.Errorf("Device already has a name")
|
||||
@@ -422,7 +422,7 @@ func TestClusterConfig(t *testing.T) {
|
||||
|
||||
wrapper, cancel := newConfigWrapper(cfg)
|
||||
defer cancel()
|
||||
m := newModel(t, wrapper, myID, "syncthing", "dev", nil)
|
||||
m := newModel(t, wrapper, myID, nil)
|
||||
m.ServeBackground()
|
||||
defer cleanupModel(m)
|
||||
|
||||
@@ -903,7 +903,7 @@ func TestIssue5063(t *testing.T) {
|
||||
defer cancel()
|
||||
|
||||
m.pmut.Lock()
|
||||
for _, c := range m.conn {
|
||||
for _, c := range m.connections {
|
||||
conn := c.(*fakeConnection)
|
||||
conn.CloseCalls(func(_ error) {})
|
||||
defer m.Closed(c, errStopped) // to unblock deferred m.Stop()
|
||||
@@ -1626,7 +1626,7 @@ func TestROScanRecovery(t *testing.T) {
|
||||
},
|
||||
})
|
||||
defer cancel()
|
||||
m := newModel(t, cfg, myID, "syncthing", "dev", nil)
|
||||
m := newModel(t, cfg, myID, nil)
|
||||
|
||||
set := newFileSet(t, "default", m.db)
|
||||
set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
|
||||
@@ -1673,7 +1673,7 @@ func TestRWScanRecovery(t *testing.T) {
|
||||
},
|
||||
})
|
||||
defer cancel()
|
||||
m := newModel(t, cfg, myID, "syncthing", "dev", nil)
|
||||
m := newModel(t, cfg, myID, nil)
|
||||
|
||||
set := newFileSet(t, "default", m.db)
|
||||
set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
|
||||
@@ -2073,7 +2073,7 @@ func TestIssue4357(t *testing.T) {
|
||||
// Create a separate wrapper not to pollute other tests.
|
||||
wrapper, cancel := newConfigWrapper(config.Configuration{Version: config.CurrentVersion})
|
||||
defer cancel()
|
||||
m := newModel(t, wrapper, myID, "syncthing", "dev", nil)
|
||||
m := newModel(t, wrapper, myID, nil)
|
||||
m.ServeBackground()
|
||||
defer cleanupModel(m)
|
||||
|
||||
@@ -2125,7 +2125,7 @@ func TestIssue4357(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIndexesForUnknownDevicesDropped(t *testing.T) {
|
||||
m := newModel(t, defaultCfgWrapper, myID, "syncthing", "dev", nil)
|
||||
m := newModel(t, defaultCfgWrapper, myID, nil)
|
||||
|
||||
files := newFileSet(t, "default", m.db)
|
||||
files.Drop(device1)
|
||||
@@ -2231,7 +2231,7 @@ func TestSharedWithClearedOnDisconnect(t *testing.T) {
|
||||
t.Error("device still in config")
|
||||
}
|
||||
|
||||
if _, ok := m.conn[device2]; ok {
|
||||
if _, ok := m.deviceConnIDs[device2]; ok {
|
||||
t.Error("conn not missing")
|
||||
}
|
||||
|
||||
@@ -2374,7 +2374,7 @@ func TestCustomMarkerName(t *testing.T) {
|
||||
|
||||
ffs := fcfg.Filesystem(nil)
|
||||
|
||||
m := newModel(t, cfg, myID, "syncthing", "dev", nil)
|
||||
m := newModel(t, cfg, myID, nil)
|
||||
|
||||
set := newFileSet(t, "default", m.db)
|
||||
set.Update(protocol.LocalDeviceID, []protocol.FileInfo{
|
||||
@@ -2736,7 +2736,7 @@ func TestIssue4094(t *testing.T) {
|
||||
// Create a separate wrapper not to pollute other tests.
|
||||
wrapper, cancel := newConfigWrapper(config.Configuration{Version: config.CurrentVersion})
|
||||
defer cancel()
|
||||
m := newModel(t, wrapper, myID, "syncthing", "dev", nil)
|
||||
m := newModel(t, wrapper, myID, nil)
|
||||
m.ServeBackground()
|
||||
defer cleanupModel(m)
|
||||
|
||||
@@ -2971,6 +2971,7 @@ func TestConnCloseOnRestart(t *testing.T) {
|
||||
br := &testutil.BlockingRW{}
|
||||
nw := &testutil.NoopRW{}
|
||||
ci := &protocolmocks.ConnectionInfo{}
|
||||
ci.ConnectionIDReturns(srand.String(16))
|
||||
m.AddConnection(protocol.NewConnection(device1, br, nw, testutil.NoopCloser{}, m, ci, protocol.CompressionNever, nil, m.keyGen), protocol.Hello{})
|
||||
m.pmut.RLock()
|
||||
if len(m.closed) != 1 {
|
||||
@@ -3639,6 +3640,7 @@ func testConfigChangeTriggersClusterConfigs(t *testing.T, expectFirst, expectSec
|
||||
})
|
||||
m.AddConnection(fc1, protocol.Hello{})
|
||||
m.AddConnection(fc2, protocol.Hello{})
|
||||
m.promoteConnections()
|
||||
|
||||
// Initial CCs
|
||||
select {
|
||||
@@ -3690,7 +3692,7 @@ func TestIssue6961(t *testing.T) {
|
||||
must(t, err)
|
||||
waiter.Wait()
|
||||
// Always recalc/repair when opening a fileset.
|
||||
m := newModel(t, wcfg, myID, "syncthing", "dev", nil)
|
||||
m := newModel(t, wcfg, myID, nil)
|
||||
m.db.Close()
|
||||
m.db, err = db.NewLowlevel(backend.OpenMemory(), m.evLogger, db.WithRecheckInterval(time.Millisecond))
|
||||
if err != nil {
|
||||
@@ -3952,7 +3954,7 @@ func TestCCFolderNotRunning(t *testing.T) {
|
||||
w, fcfg, wCancel := newDefaultCfgWrapper()
|
||||
defer wCancel()
|
||||
tfs := fcfg.Filesystem(nil)
|
||||
m := newModel(t, w, myID, "syncthing", "dev", nil)
|
||||
m := newModel(t, w, myID, nil)
|
||||
defer cleanupModelAndRemoveDir(m, tfs.URI())
|
||||
|
||||
// A connection can happen before all the folders are started.
|
||||
|
||||
@@ -1214,7 +1214,7 @@ func TestRequestIndexSenderClusterConfigBeforeStart(t *testing.T) {
|
||||
|
||||
// Initialise db with an entry and then stop everything again
|
||||
must(t, tfs.Mkdir(dir1, 0o777))
|
||||
m := newModel(t, w, myID, "syncthing", "dev", nil)
|
||||
m := newModel(t, w, myID, nil)
|
||||
defer cleanupModelAndRemoveDir(m, tfs.URI())
|
||||
m.ServeBackground()
|
||||
m.ScanFolders()
|
||||
@@ -1223,7 +1223,7 @@ func TestRequestIndexSenderClusterConfigBeforeStart(t *testing.T) {
|
||||
|
||||
// Add connection (sends incoming cluster config) before starting the new model
|
||||
m = &testModel{
|
||||
model: NewModel(m.cfg, m.id, m.clientName, m.clientVersion, m.db, m.protectedFiles, m.evLogger, protocol.NewKeyGenerator()).(*model),
|
||||
model: NewModel(m.cfg, m.id, m.db, m.protectedFiles, m.evLogger, protocol.NewKeyGenerator()).(*model),
|
||||
evCancel: m.evCancel,
|
||||
stopped: make(chan struct{}),
|
||||
}
|
||||
|
||||
@@ -39,7 +39,9 @@ func init() {
|
||||
device1, _ = protocol.DeviceIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR")
|
||||
device2, _ = protocol.DeviceIDFromString("GYRZZQB-IRNPV4Z-T7TC52W-EQYJ3TT-FDQW6MW-DFLMU42-SSSU6EM-FBK2VAY")
|
||||
device1Conn.DeviceIDReturns(device1)
|
||||
device1Conn.ConnectionIDReturns(rand.String(16))
|
||||
device2Conn.DeviceIDReturns(device2)
|
||||
device2Conn.ConnectionIDReturns(rand.String(16))
|
||||
|
||||
cfg := config.New(myID)
|
||||
cfg.Options.MinHomeDiskFree.Value = 0 // avoids unnecessary free space checks
|
||||
@@ -127,7 +129,7 @@ func setupModelWithConnectionFromWrapper(t testing.TB, w config.Wrapper) (*testM
|
||||
|
||||
func setupModel(t testing.TB, w config.Wrapper) *testModel {
|
||||
t.Helper()
|
||||
m := newModel(t, w, myID, "syncthing", "dev", nil)
|
||||
m := newModel(t, w, myID, nil)
|
||||
m.ServeBackground()
|
||||
<-m.started
|
||||
|
||||
@@ -144,14 +146,14 @@ type testModel struct {
|
||||
stopped chan struct{}
|
||||
}
|
||||
|
||||
func newModel(t testing.TB, cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersion string, protectedFiles []string) *testModel {
|
||||
func newModel(t testing.TB, cfg config.Wrapper, id protocol.DeviceID, protectedFiles []string) *testModel {
|
||||
t.Helper()
|
||||
evLogger := events.NewLogger()
|
||||
ldb, err := db.NewLowlevel(backend.OpenMemory(), evLogger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m := NewModel(cfg, id, clientName, clientVersion, ldb, protectedFiles, evLogger, protocol.NewKeyGenerator()).(*model)
|
||||
m := NewModel(cfg, id, ldb, protectedFiles, evLogger, protocol.NewKeyGenerator()).(*model)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go evLogger.Serve(ctx)
|
||||
return &testModel{
|
||||
|
||||
Reference in New Issue
Block a user