syncthing/lib/connections/registry/registry.go
Jakob Borg c6334e61aa
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>
2023-09-06 12:52:01 +02:00

83 lines
2.1 KiB
Go

// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
// Registry tracks connections/addresses on which we are listening on, to allow us to pick a connection/address that
// has a NAT port mapping. This also makes our outgoing port stable and same as incoming port which should allow
// better probability of punching through.
package registry
import (
"strings"
"github.com/syncthing/syncthing/lib/sliceutil"
"github.com/syncthing/syncthing/lib/sync"
)
type Registry struct {
mut sync.Mutex
available map[string][]interface{}
}
func New() *Registry {
return &Registry{
mut: sync.NewMutex(),
available: make(map[string][]interface{}),
}
}
func (r *Registry) Register(scheme string, item interface{}) {
r.mut.Lock()
defer r.mut.Unlock()
r.available[scheme] = append(r.available[scheme], item)
}
func (r *Registry) Unregister(scheme string, item interface{}) {
r.mut.Lock()
defer r.mut.Unlock()
candidates := r.available[scheme]
for i, existingItem := range candidates {
if existingItem == item {
r.available[scheme] = sliceutil.RemoveAndZero(candidates, i)
break
}
}
}
// Get returns an item for a schema compatible with the given scheme.
// If any item satisfies preferred, that has precedence over other items.
func (r *Registry) Get(scheme string, preferred func(interface{}) bool) interface{} {
r.mut.Lock()
defer r.mut.Unlock()
var (
best interface{}
bestPref bool
bestScheme string
)
for availableScheme, items := range r.available {
// quic:// should be considered ok for both quic4:// and quic6://
if !strings.HasPrefix(scheme, availableScheme) {
continue
}
for _, item := range items {
better := best == nil
pref := preferred(item)
if !better {
// In case of a tie, prefer "quic" to "quic[46]" etc.
better = pref &&
(!bestPref || len(availableScheme) < len(bestScheme))
}
if !better {
continue
}
best, bestPref, bestScheme = item, pref, availableScheme
}
}
return best
}