all: Send Close BEP msg on intentional disconnect (#5440)
This avoids waiting until next ping and timeout until the connection is actually closed both by notifying the peer of the disconnect and by immediately closing the local end of the connection after that. As a nice side effect, info level logging about dropped connections now have the actual reason in it, not a generic timeout error which looks like a real problem with the connection.
This commit is contained in:
@@ -8,11 +8,9 @@ package model
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
@@ -128,6 +126,9 @@ var (
|
||||
errFolderNotRunning = errors.New("folder is not running")
|
||||
errFolderMissing = errors.New("no such folder")
|
||||
errNetworkNotAllowed = errors.New("network not allowed")
|
||||
// errors about why a connection is closed
|
||||
errIgnoredFolderRemoved = errors.New("folder no longer ignored")
|
||||
errReplacingConnection = errors.New("replacing connection")
|
||||
)
|
||||
|
||||
// NewModel creates and starts a new model. The model starts in read-only mode,
|
||||
@@ -226,7 +227,7 @@ func (m *Model) startFolderLocked(folder string) config.FolderType {
|
||||
|
||||
// Close connections to affected devices
|
||||
for _, id := range cfg.DeviceIDs() {
|
||||
m.closeLocked(id)
|
||||
m.closeLocked(id, fmt.Errorf("started folder %v", cfg.Description()))
|
||||
}
|
||||
|
||||
v, ok := fs.Sequence(protocol.LocalDeviceID), true
|
||||
@@ -339,7 +340,7 @@ func (m *Model) RemoveFolder(cfg config.FolderConfiguration) {
|
||||
// Delete syncthing specific files
|
||||
cfg.Filesystem().RemoveAll(config.DefaultMarkerName)
|
||||
|
||||
m.tearDownFolderLocked(cfg)
|
||||
m.tearDownFolderLocked(cfg, fmt.Errorf("removing folder %v", cfg.Description()))
|
||||
// Remove it from the database
|
||||
db.DropFolder(m.db, cfg.ID)
|
||||
|
||||
@@ -347,12 +348,12 @@ func (m *Model) RemoveFolder(cfg config.FolderConfiguration) {
|
||||
m.fmut.Unlock()
|
||||
}
|
||||
|
||||
func (m *Model) tearDownFolderLocked(cfg config.FolderConfiguration) {
|
||||
func (m *Model) tearDownFolderLocked(cfg config.FolderConfiguration, err error) {
|
||||
// Close connections to affected devices
|
||||
// Must happen before stopping the folder service to abort ongoing
|
||||
// transmissions and thus allow timely service termination.
|
||||
for _, dev := range cfg.Devices {
|
||||
m.closeLocked(dev.DeviceID)
|
||||
m.closeLocked(dev.DeviceID, err)
|
||||
}
|
||||
|
||||
// Stop the services running for this folder and wait for them to finish
|
||||
@@ -398,14 +399,26 @@ func (m *Model) RestartFolder(from, to config.FolderConfiguration) {
|
||||
defer m.fmut.Unlock()
|
||||
defer m.pmut.Unlock()
|
||||
|
||||
m.tearDownFolderLocked(from)
|
||||
if to.Paused {
|
||||
l.Infoln("Paused folder", to.Description())
|
||||
} else {
|
||||
m.addFolderLocked(to)
|
||||
folderType := m.startFolderLocked(to.ID)
|
||||
l.Infoln("Restarted folder", to.Description(), fmt.Sprintf("(%s)", folderType))
|
||||
var infoMsg string
|
||||
var errMsg string
|
||||
switch {
|
||||
case to.Paused:
|
||||
infoMsg = "Paused"
|
||||
errMsg = "pausing"
|
||||
case from.Paused:
|
||||
infoMsg = "Unpaused"
|
||||
errMsg = "unpausing"
|
||||
default:
|
||||
infoMsg = "Restarted"
|
||||
errMsg = "restarting"
|
||||
}
|
||||
|
||||
m.tearDownFolderLocked(from, fmt.Errorf("%v folder %v", errMsg, to.Description()))
|
||||
if !to.Paused {
|
||||
m.addFolderLocked(to)
|
||||
m.startFolderLocked(to.ID)
|
||||
}
|
||||
l.Infof("%v folder %v (%v)", infoMsg, to.Description(), to.Type)
|
||||
}
|
||||
|
||||
func (m *Model) UsageReportingStats(version int, preview bool) map[string]interface{} {
|
||||
@@ -1342,21 +1355,21 @@ func (m *Model) Closed(conn protocol.Connection, err error) {
|
||||
}
|
||||
|
||||
// close will close the underlying connection for a given device
|
||||
func (m *Model) close(device protocol.DeviceID) {
|
||||
func (m *Model) close(device protocol.DeviceID, err error) {
|
||||
m.pmut.Lock()
|
||||
m.closeLocked(device)
|
||||
m.closeLocked(device, err)
|
||||
m.pmut.Unlock()
|
||||
}
|
||||
|
||||
// closeLocked will close the underlying connection for a given device
|
||||
func (m *Model) closeLocked(device protocol.DeviceID) {
|
||||
func (m *Model) closeLocked(device protocol.DeviceID, err error) {
|
||||
conn, ok := m.conn[device]
|
||||
if !ok {
|
||||
// There is no connection to close
|
||||
return
|
||||
}
|
||||
|
||||
closeRawConn(conn)
|
||||
conn.Close(err)
|
||||
}
|
||||
|
||||
// Implements protocol.RequestResponse
|
||||
@@ -1719,7 +1732,7 @@ func (m *Model) AddConnection(conn connections.Connection, hello protocol.HelloR
|
||||
// back into Closed() for the cleanup.
|
||||
closed := m.closed[deviceID]
|
||||
m.pmut.Unlock()
|
||||
closeRawConn(oldConn)
|
||||
oldConn.Close(errReplacingConnection)
|
||||
<-closed
|
||||
m.pmut.Lock()
|
||||
}
|
||||
@@ -2582,6 +2595,10 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
|
||||
continue
|
||||
}
|
||||
|
||||
if fromCfg.Paused && toCfg.Paused {
|
||||
continue
|
||||
}
|
||||
|
||||
// This folder exists on both sides. Settings might have changed.
|
||||
// Check if anything differs that requires a restart.
|
||||
if !reflect.DeepEqual(fromCfg.RequiresRestartOnly(), toCfg.RequiresRestartOnly()) {
|
||||
@@ -2616,12 +2633,12 @@ func (m *Model) CommitConfiguration(from, to config.Configuration) bool {
|
||||
|
||||
// Ignored folder was removed, reconnect to retrigger the prompt.
|
||||
if len(fromCfg.IgnoredFolders) > len(toCfg.IgnoredFolders) {
|
||||
m.close(deviceID)
|
||||
m.close(deviceID, errIgnoredFolderRemoved)
|
||||
}
|
||||
|
||||
if toCfg.Paused {
|
||||
l.Infoln("Pausing", deviceID)
|
||||
m.close(deviceID)
|
||||
m.close(deviceID, errDevicePaused)
|
||||
events.Default.Log(events.DevicePaused, map[string]string{"device": deviceID.String()})
|
||||
} else {
|
||||
events.Default.Log(events.DeviceResumed, map[string]string{"device": deviceID.String()})
|
||||
@@ -2717,17 +2734,6 @@ func getChunk(data []string, skip, get int) ([]string, int, int) {
|
||||
return data[skip : skip+get], 0, 0
|
||||
}
|
||||
|
||||
func closeRawConn(conn io.Closer) error {
|
||||
if conn, ok := conn.(*tls.Conn); ok {
|
||||
// If the underlying connection is a *tls.Conn, Close() does more
|
||||
// than it says on the tin. Specifically, it sends a TLS alert
|
||||
// message, which might block forever if the connection is dead
|
||||
// and we don't have a deadline set.
|
||||
conn.SetWriteDeadline(time.Now().Add(250 * time.Millisecond))
|
||||
}
|
||||
return conn.Close()
|
||||
}
|
||||
|
||||
func stringSliceWithout(ss []string, s string) []string {
|
||||
for i := range ss {
|
||||
if ss[i] == s {
|
||||
|
||||
@@ -316,11 +316,10 @@ type fakeConnection struct {
|
||||
mut sync.Mutex
|
||||
}
|
||||
|
||||
func (f *fakeConnection) Close() error {
|
||||
func (f *fakeConnection) Close(_ error) {
|
||||
f.mut.Lock()
|
||||
defer f.mut.Unlock()
|
||||
f.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeConnection) Start() {
|
||||
|
||||
Reference in New Issue
Block a user