2
0
mirror of synced 2025-02-22 13:48:21 +00:00

Try to balance incoming and outgoing conns per torrent

This commit is contained in:
Matt Joiner 2023-04-22 15:42:13 +10:00
parent fdb0911e28
commit 79ab1ffe2b
No known key found for this signature in database
GPG Key ID: 6B990B8185E7F782
3 changed files with 114 additions and 23 deletions

View File

@ -459,7 +459,7 @@ func (cl *Client) wantConns() bool {
return true
}
for _, t := range cl.torrents {
if t.wantConns() {
if t.wantIncomingConns() {
return true
}
}

View File

@ -1090,17 +1090,26 @@ func getPeerConnSlice(cap int) []*PeerConn {
}
}
// The worst connection is one that hasn't been sent, or sent anything useful for the longest. A bad
// connection is one that usually sends us unwanted pieces, or has been in the worse half of the
// established connections for more than a minute. This is O(n log n). If there was a way to not
// consider the position of a conn relative to the total number, it could be reduced to O(n).
func (t *Torrent) worstBadConn() (ret *PeerConn) {
wcs := worseConnSlice{conns: t.appendUnclosedConns(getPeerConnSlice(len(t.conns)))}
defer peerConnSlices.Put(wcs.conns)
wcs.initKeys()
// Calls the given function with a slice of unclosed conns. It uses a pool to reduce allocations as
// this is a frequent occurrence.
func (t *Torrent) withUnclosedConns(f func([]*PeerConn)) {
sl := t.appendUnclosedConns(getPeerConnSlice(len(t.conns)))
f(sl)
peerConnSlices.Put(sl)
}
func (t *Torrent) worstBadConnFromSlice(opts worseConnLensOpts, sl []*PeerConn) *PeerConn {
wcs := worseConnSlice{conns: sl}
wcs.initKeys(opts)
heap.Init(&wcs)
for wcs.Len() != 0 {
c := heap.Pop(&wcs).(*PeerConn)
if opts.incomingIsBad && !c.outgoing {
return c
}
if opts.outgoingIsBad && c.outgoing {
return c
}
if c._stats.ChunksReadWasted.Int64() >= 6 && c._stats.ChunksReadWasted.Int64() > c._stats.ChunksReadUseful.Int64() {
return c
}
@ -1116,6 +1125,17 @@ func (t *Torrent) worstBadConn() (ret *PeerConn) {
return nil
}
// The worst connection is one that hasn't been sent, or sent anything useful for the longest. A bad
// connection is one that usually sends us unwanted pieces, or has been in the worse half of the
// established connections for more than a minute. This is O(n log n). If there was a way to not
// consider the position of a conn relative to the total number, it could be reduced to O(n).
func (t *Torrent) worstBadConn(opts worseConnLensOpts) (ret *PeerConn) {
t.withUnclosedConns(func(ucs []*PeerConn) {
ret = t.worstBadConnFromSlice(opts, ucs)
})
return
}
type PieceStateChange struct {
Index int
PieceState
@ -1303,6 +1323,15 @@ func (t *Torrent) numReceivedConns() (ret int) {
return
}
func (t *Torrent) numOutgoingConns() (ret int) {
for c := range t.conns {
if c.outgoing {
ret++
}
}
return
}
func (t *Torrent) maxHalfOpen() int {
// Note that if we somehow exceed the maximum established conns, we want
// the negative value to have an effect.
@ -1310,13 +1339,16 @@ func (t *Torrent) maxHalfOpen() int {
extraIncoming := int64(t.numReceivedConns() - t.maxEstablishedConns/2)
// We want to allow some experimentation with new peers, and to try to
// upset an oversupply of received connections.
return int(min(max(5, extraIncoming)+establishedHeadroom, int64(t.cl.config.HalfOpenConnsPerTorrent)))
return int(min(
max(5, extraIncoming)+establishedHeadroom,
int64(t.cl.config.HalfOpenConnsPerTorrent),
))
}
func (t *Torrent) openNewConns() (initiated int) {
defer t.updateWantPeersEvent()
for t.peers.Len() != 0 {
if !t.wantConns() {
if !t.wantOutgoingConns() {
return
}
if len(t.halfOpen) >= t.maxHalfOpen() {
@ -1542,7 +1574,7 @@ func (t *Torrent) wantPeers() bool {
if t.peers.Len() > t.cl.config.TorrentPeersLowWater {
return false
}
return t.wantConns()
return t.wantOutgoingConns()
}
func (t *Torrent) updateWantPeersEvent() {
@ -1815,7 +1847,7 @@ func (t *Torrent) dhtAnnouncer(s DhtServer) {
// We're also announcing ourselves as a listener, so we don't just want peer addresses.
// TODO: We can include the announce_peer step depending on whether we can receive
// inbound connections. We should probably only announce once every 15 mins too.
if !t.wantConns() {
if !t.wantAnyConns() {
goto wait
}
// TODO: Determine if there's a listener on the port we're announcing.
@ -1933,9 +1965,16 @@ func (t *Torrent) addPeerConn(c *PeerConn) (err error) {
}
}
if len(t.conns) >= t.maxEstablishedConns {
c := t.worstBadConn()
numOutgoing := t.numOutgoingConns()
numIncoming := len(t.conns) - numOutgoing
c := t.worstBadConn(worseConnLensOpts{
// We've already established that we have too many connections at this point, so we just
// need to match what kind we have too many of vs. what we're trying to add now.
incomingIsBad: (numIncoming-numOutgoing > 1) && c.outgoing,
outgoingIsBad: (numOutgoing-numIncoming > 1) && !c.outgoing,
})
if c == nil {
return errors.New("don't want conns")
return errors.New("don't want conn")
}
c.close()
t.deletePeerConn(c)
@ -1950,7 +1989,7 @@ func (t *Torrent) addPeerConn(c *PeerConn) (err error) {
return nil
}
func (t *Torrent) wantConns() bool {
func (t *Torrent) newConnsAllowed() bool {
if !t.networkingEnabled.Bool() {
return false
}
@ -1960,7 +1999,48 @@ func (t *Torrent) wantConns() bool {
if !t.needData() && (!t.seeding() || !t.haveAnyPieces()) {
return false
}
return len(t.conns) < t.maxEstablishedConns || t.worstBadConn() != nil
return true
}
func (t *Torrent) wantAnyConns() bool {
if !t.networkingEnabled.Bool() {
return false
}
if t.closed.IsSet() {
return false
}
if !t.needData() && (!t.seeding() || !t.haveAnyPieces()) {
return false
}
return len(t.conns) < t.maxEstablishedConns
}
func (t *Torrent) wantOutgoingConns() bool {
if !t.newConnsAllowed() {
return false
}
if len(t.conns) < t.maxEstablishedConns {
return true
}
numIncomingConns := len(t.conns) - t.numOutgoingConns()
return t.worstBadConn(worseConnLensOpts{
incomingIsBad: numIncomingConns-t.numOutgoingConns() > 1,
outgoingIsBad: false,
}) != nil
}
func (t *Torrent) wantIncomingConns() bool {
if !t.newConnsAllowed() {
return false
}
if len(t.conns) < t.maxEstablishedConns {
return true
}
numIncomingConns := len(t.conns) - t.numOutgoingConns()
return t.worstBadConn(worseConnLensOpts{
incomingIsBad: false,
outgoingIsBad: t.numOutgoingConns()-numIncomingConns > 1,
}) != nil
}
func (t *Torrent) SetMaxEstablishedConns(max int) (oldMax int) {
@ -1973,7 +2053,7 @@ func (t *Torrent) SetMaxEstablishedConns(max int) (oldMax int) {
return true
}),
}
wcs.initKeys()
wcs.initKeys(worseConnLensOpts{})
heap.Init(&wcs)
for len(t.conns) > t.maxEstablishedConns && wcs.Len() > 0 {
t.dropConnection(heap.Pop(&wcs).(*PeerConn))

View File

@ -11,6 +11,7 @@ import (
)
type worseConnInput struct {
BadDirection bool
Useful bool
LastHelpful time.Time
CompletedHandshake time.Time
@ -29,7 +30,11 @@ func (me *worseConnInput) doGetPeerPriorityOnce() {
me.getPeerPriorityOnce.Do(me.doGetPeerPriority)
}
func worseConnInputFromPeer(p *Peer) worseConnInput {
type worseConnLensOpts struct {
incomingIsBad, outgoingIsBad bool
}
func worseConnInputFromPeer(p *Peer, opts worseConnLensOpts) worseConnInput {
ret := worseConnInput{
Useful: p.useful(),
LastHelpful: p.lastHelpful(),
@ -37,18 +42,24 @@ func worseConnInputFromPeer(p *Peer) worseConnInput {
Pointer: uintptr(unsafe.Pointer(p)),
GetPeerPriority: p.peerPriority,
}
if opts.incomingIsBad && !p.outgoing {
ret.BadDirection = true
} else if opts.outgoingIsBad && p.outgoing {
ret.BadDirection = true
}
return ret
}
func worseConn(_l, _r *Peer) bool {
// TODO: Use generics for ptr to
l := worseConnInputFromPeer(_l)
r := worseConnInputFromPeer(_r)
l := worseConnInputFromPeer(_l, worseConnLensOpts{})
r := worseConnInputFromPeer(_r, worseConnLensOpts{})
return l.Less(&r)
}
func (l *worseConnInput) Less(r *worseConnInput) bool {
less, ok := multiless.New().Bool(
r.BadDirection, l.BadDirection).Bool(
l.Useful, r.Useful).CmpInt64(
l.LastHelpful.Sub(r.LastHelpful).Nanoseconds()).CmpInt64(
l.CompletedHandshake.Sub(r.CompletedHandshake).Nanoseconds()).LazySameLess(
@ -80,10 +91,10 @@ type worseConnSlice struct {
keys []worseConnInput
}
func (me *worseConnSlice) initKeys() {
func (me *worseConnSlice) initKeys(opts worseConnLensOpts) {
me.keys = make([]worseConnInput, len(me.conns))
for i, c := range me.conns {
me.keys[i] = worseConnInputFromPeer(&c.Peer)
me.keys[i] = worseConnInputFromPeer(&c.Peer, opts)
}
}