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
+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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user