all: Support multiple device connections (fixes #141) (#8918)

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:
Jakob Borg
2023-09-06 12:52:01 +02:00
committed by GitHub
co-authored by Simon Frei tomasz1986 bt90
parent 38bbdebffa
commit c6334e61aa
41 changed files with 1640 additions and 933 deletions
+17 -9
View File
@@ -136,9 +136,9 @@ type Model interface {
DownloadProgress(conn Connection, folder string, updates []FileDownloadProgressUpdate) error
}
// contextLessModel is the Model interface, but without the initial
// Connection parameter. Internal use only.
type contextLessModel interface {
// rawModel is the Model interface, but without the initial Connection
// parameter. Internal use only.
type rawModel interface {
Index(folder string, files []FileInfo) error
IndexUpdate(folder string, files []FileInfo) error
Request(folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error)
@@ -177,6 +177,7 @@ type ConnectionInfo interface {
String() string
Crypto() string
EstablishedAt() time.Time
ConnectionID() string
}
type rawConnection struct {
@@ -184,8 +185,9 @@ type rawConnection struct {
deviceID DeviceID
idString string
model contextLessModel
model rawModel
startTime time.Time
started chan struct{}
cr *countingReader
cw *countingWriter
@@ -263,7 +265,7 @@ func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, closer
return wc
}
func newRawConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, closer io.Closer, receiver contextLessModel, connInfo ConnectionInfo, compress Compression) *rawConnection {
func newRawConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, closer io.Closer, receiver rawModel, connInfo ConnectionInfo, compress Compression) *rawConnection {
idString := deviceID.String()
cr := &countingReader{Reader: reader, idString: idString}
cw := &countingWriter{Writer: writer, idString: idString}
@@ -274,6 +276,7 @@ func newRawConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, clo
deviceID: deviceID,
idString: deviceID.String(),
model: receiver,
started: make(chan struct{}),
cr: cr,
cw: cw,
closer: closer,
@@ -315,6 +318,7 @@ func (c *rawConnection) Start() {
c.loopWG.Done()
}()
c.startTime = time.Now().Truncate(time.Second)
close(c.started)
}
func (c *rawConnection) DeviceID() DeviceID {
@@ -960,9 +964,9 @@ func (c *rawConnection) Close(err error) {
// internalClose is called if there is an unexpected error during normal operation.
func (c *rawConnection) internalClose(err error) {
c.closeOnce.Do(func() {
l.Debugln("close due to", err)
l.Debugf("close connection to %s at %s due to %v", c.deviceID.Short(), c.ConnectionInfo, err)
if cerr := c.closer.Close(); cerr != nil {
l.Debugln(c.deviceID, "failed to close underlying conn:", cerr)
l.Debugf("failed to close underlying conn %s at %s %v:", c.deviceID.Short(), c.ConnectionInfo, cerr)
}
close(c.closed)
@@ -975,7 +979,11 @@ func (c *rawConnection) internalClose(err error) {
}
c.awaitingMut.Unlock()
<-c.dispatcherLoopStopped
if !c.startTime.IsZero() {
// Wait for the dispatcher loop to exit, if it was started to
// begin with.
<-c.dispatcherLoopStopped
}
c.model.Closed(err)
})
@@ -1108,7 +1116,7 @@ func messageContext(msg message) (string, error) {
// connectionWrappingModel takes the Model interface from the model package,
// which expects the Connection as the first parameter in all methods, and
// wraps it to conform to the protocol.contextLessModel interface.
// wraps it to conform to the rawModel interface.
type connectionWrappingModel struct {
conn Connection
model Model