Improve uploading/seeding
This commit is contained in:
parent
764f5db512
commit
ced5733c88
124
client.go
124
client.go
|
@ -232,7 +232,7 @@ func (cl *Client) WriteStatus(_w io.Writer) {
|
||||||
w.WriteString("<missing metainfo>")
|
w.WriteString("<missing metainfo>")
|
||||||
}
|
}
|
||||||
fmt.Fprint(w, "\n")
|
fmt.Fprint(w, "\n")
|
||||||
t.writeStatus(w)
|
t.writeStatus(w, cl)
|
||||||
fmt.Fprintln(w)
|
fmt.Fprintln(w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -342,7 +342,6 @@ func (t *torrent) connPendPiece(c *connection, piece int) {
|
||||||
func (cl *Client) raisePiecePriority(t *torrent, piece int, priority piecePriority) {
|
func (cl *Client) raisePiecePriority(t *torrent, piece int, priority piecePriority) {
|
||||||
if t.Pieces[piece].Priority < priority {
|
if t.Pieces[piece].Priority < priority {
|
||||||
cl.prioritizePiece(t, piece, priority)
|
cl.prioritizePiece(t, piece, priority)
|
||||||
cl.event.Broadcast()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1404,6 +1403,54 @@ func (cl *Client) peerHasAll(t *torrent, cn *connection) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (me *Client) upload(t *torrent, c *connection) {
|
||||||
|
if me.config.NoUpload {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !c.PeerInterested {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !me.seeding(t) && !t.connHasWantedPieces(c) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
another:
|
||||||
|
for c.chunksSent < c.UsefulChunksReceived+6 {
|
||||||
|
c.Unchoke()
|
||||||
|
for r := range c.PeerRequests {
|
||||||
|
err := me.sendChunk(t, c, r)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error sending chunk to peer: %s", err)
|
||||||
|
}
|
||||||
|
delete(c.PeerRequests, r)
|
||||||
|
goto another
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Choke()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (me *Client) sendChunk(t *torrent, c *connection, r request) error {
|
||||||
|
b := make([]byte, r.Length)
|
||||||
|
p := t.Info.Piece(int(r.Index))
|
||||||
|
n, err := dataReadAt(t.data, b, p.Offset()+int64(r.Begin))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n != len(b) {
|
||||||
|
log.Fatal(b)
|
||||||
|
}
|
||||||
|
c.Post(pp.Message{
|
||||||
|
Type: pp.Piece,
|
||||||
|
Index: r.Index,
|
||||||
|
Begin: r.Begin,
|
||||||
|
Piece: b,
|
||||||
|
})
|
||||||
|
uploadChunksPosted.Add(1)
|
||||||
|
c.chunksSent++
|
||||||
|
c.lastChunkSent = time.Now()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Processes incoming bittorrent messages. The client lock is held upon entry
|
// Processes incoming bittorrent messages. The client lock is held upon entry
|
||||||
// and exit.
|
// and exit.
|
||||||
func (me *Client) connectionLoop(t *torrent, c *connection) error {
|
func (me *Client) connectionLoop(t *torrent, c *connection) error {
|
||||||
|
@ -1448,11 +1495,7 @@ func (me *Client) connectionLoop(t *torrent, c *connection) error {
|
||||||
me.peerUnchoked(t, c)
|
me.peerUnchoked(t, c)
|
||||||
case pp.Interested:
|
case pp.Interested:
|
||||||
c.PeerInterested = true
|
c.PeerInterested = true
|
||||||
// TODO: This should be done from a dedicated unchoking routine.
|
me.upload(t, c)
|
||||||
if me.config.NoUpload {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
c.Unchoke()
|
|
||||||
case pp.NotInterested:
|
case pp.NotInterested:
|
||||||
c.PeerInterested = false
|
c.PeerInterested = false
|
||||||
c.Choke()
|
c.Choke()
|
||||||
|
@ -1462,30 +1505,15 @@ func (me *Client) connectionLoop(t *torrent, c *connection) error {
|
||||||
if c.Choked {
|
if c.Choked {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
request := newRequest(msg.Index, msg.Begin, msg.Length)
|
if !c.PeerInterested {
|
||||||
// TODO: Requests should be satisfied from a dedicated upload
|
err = errors.New("peer sent request but isn't interested")
|
||||||
// routine.
|
break
|
||||||
// c.PeerRequests[request] = struct{}{}
|
|
||||||
// if c.PeerRequests == nil {
|
|
||||||
// c.PeerRequests = make(map[request]struct{}, maxRequests)
|
|
||||||
// }
|
|
||||||
p := make([]byte, msg.Length)
|
|
||||||
n, err := dataReadAt(t.data, p, int64(t.pieceLength(0))*int64(msg.Index)+int64(msg.Begin))
|
|
||||||
// TODO: Failing to read for a request should not be fatal to the connection.
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("reading t data to serve request %q: %s", request, err)
|
|
||||||
}
|
}
|
||||||
if n != int(msg.Length) {
|
if c.PeerRequests == nil {
|
||||||
return fmt.Errorf("bad request: %v", msg)
|
c.PeerRequests = make(map[request]struct{}, maxRequests)
|
||||||
}
|
}
|
||||||
c.Post(pp.Message{
|
c.PeerRequests[newRequest(msg.Index, msg.Begin, msg.Length)] = struct{}{}
|
||||||
Type: pp.Piece,
|
me.upload(t, c)
|
||||||
Index: msg.Index,
|
|
||||||
Begin: msg.Begin,
|
|
||||||
Piece: p,
|
|
||||||
})
|
|
||||||
uploadChunksPosted.Add(1)
|
|
||||||
c.chunksSent++
|
|
||||||
case pp.Cancel:
|
case pp.Cancel:
|
||||||
req := newRequest(msg.Index, msg.Begin, msg.Length)
|
req := newRequest(msg.Index, msg.Begin, msg.Length)
|
||||||
if !c.PeerCancel(req) {
|
if !c.PeerCancel(req) {
|
||||||
|
@ -1699,7 +1727,7 @@ func (me *Client) addConnection(t *torrent, c *connection) bool {
|
||||||
// TODO: This should probably be done by a routine that kills off bad
|
// TODO: This should probably be done by a routine that kills off bad
|
||||||
// connections, and extra connections killed here instead.
|
// connections, and extra connections killed here instead.
|
||||||
if len(t.Conns) > socketsPerTorrent {
|
if len(t.Conns) > socketsPerTorrent {
|
||||||
wcs := t.worstConnsHeap()
|
wcs := t.worstConnsHeap(me)
|
||||||
heap.Pop(wcs).(*connection).Close()
|
heap.Pop(wcs).(*connection).Close()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -1717,26 +1745,29 @@ func (t *torrent) needData() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: I'm sure there's something here to do with seeding.
|
func (cl *Client) usefulConn(t *torrent, c *connection) bool {
|
||||||
func (t *torrent) badConn(c *connection) bool {
|
|
||||||
// A 30 second grace for initial messages to go through.
|
// A 30 second grace for initial messages to go through.
|
||||||
if time.Since(c.completedHandshake) < 30*time.Second {
|
if time.Since(c.completedHandshake) < 30*time.Second {
|
||||||
return false
|
return true
|
||||||
}
|
}
|
||||||
if !t.haveInfo() {
|
if !t.haveInfo() {
|
||||||
if !c.supportsExtension("ut_metadata") {
|
if !c.supportsExtension("ut_metadata") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if time.Since(c.completedHandshake) < 2*time.Minute {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if time.Since(c.completedHandshake) > 2*time.Minute {
|
return false
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
if cl.seeding(t) {
|
||||||
|
return c.PeerInterested
|
||||||
}
|
}
|
||||||
return !t.connHasWantedPieces(c)
|
return t.connHasWantedPieces(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *torrent) numGoodConns() (num int) {
|
func (t *torrent) numGoodConns(cl *Client) (num int) {
|
||||||
for _, c := range t.Conns {
|
for _, c := range t.Conns {
|
||||||
if !t.badConn(c) {
|
if cl.usefulConn(t, c) {
|
||||||
num++
|
num++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1744,10 +1775,10 @@ func (t *torrent) numGoodConns() (num int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (me *Client) wantConns(t *torrent) bool {
|
func (me *Client) wantConns(t *torrent) bool {
|
||||||
if me.config.NoUpload && !t.needData() {
|
if !me.seeding(t) && !t.needData() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if t.numGoodConns() >= socketsPerTorrent {
|
if t.numGoodConns(me) >= socketsPerTorrent {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -2228,7 +2259,16 @@ func (cl *Client) waitWantPeers(t *torrent) bool {
|
||||||
|
|
||||||
// Returns whether the client should make effort to seed the torrent.
|
// Returns whether the client should make effort to seed the torrent.
|
||||||
func (cl *Client) seeding(t *torrent) bool {
|
func (cl *Client) seeding(t *torrent) bool {
|
||||||
return cl.config.Seed && !cl.config.NoUpload
|
if cl.config.NoUpload {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !cl.config.Seed {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if t.needData() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *Client) announceTorrentDHT(t *torrent, impliedPort bool) {
|
func (cl *Client) announceTorrentDHT(t *torrent, impliedPort bool) {
|
||||||
|
@ -2515,6 +2555,8 @@ func (me *Client) downloadedChunk(t *torrent, c *connection, msg *pp.Message) er
|
||||||
c.UsefulChunksReceived++
|
c.UsefulChunksReceived++
|
||||||
c.lastUsefulChunkReceived = time.Now()
|
c.lastUsefulChunkReceived = time.Now()
|
||||||
|
|
||||||
|
me.upload(t, c)
|
||||||
|
|
||||||
// Write the chunk out.
|
// Write the chunk out.
|
||||||
err := t.writeChunk(int(msg.Index), int64(msg.Begin), msg.Piece)
|
err := t.writeChunk(int(msg.Index), int64(msg.Begin), msg.Piece)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -18,8 +18,8 @@ import (
|
||||||
"gopkg.in/check.v1"
|
"gopkg.in/check.v1"
|
||||||
|
|
||||||
"github.com/anacrolix/torrent/bencode"
|
"github.com/anacrolix/torrent/bencode"
|
||||||
"github.com/anacrolix/torrent/data/blob"
|
|
||||||
"github.com/anacrolix/torrent/data"
|
"github.com/anacrolix/torrent/data"
|
||||||
|
"github.com/anacrolix/torrent/data/blob"
|
||||||
"github.com/anacrolix/torrent/internal/testutil"
|
"github.com/anacrolix/torrent/internal/testutil"
|
||||||
"github.com/anacrolix/torrent/metainfo"
|
"github.com/anacrolix/torrent/metainfo"
|
||||||
"github.com/anacrolix/torrent/util"
|
"github.com/anacrolix/torrent/util"
|
||||||
|
@ -251,6 +251,7 @@ func TestClientTransfer(t *testing.T) {
|
||||||
greetingTempDir, mi := testutil.GreetingTestTorrent()
|
greetingTempDir, mi := testutil.GreetingTestTorrent()
|
||||||
defer os.RemoveAll(greetingTempDir)
|
defer os.RemoveAll(greetingTempDir)
|
||||||
cfg := TestingConfig
|
cfg := TestingConfig
|
||||||
|
cfg.Seed = true
|
||||||
cfg.DataDir = greetingTempDir
|
cfg.DataDir = greetingTempDir
|
||||||
seeder, err := NewClient(&cfg)
|
seeder, err := NewClient(&cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -52,6 +52,7 @@ type connection struct {
|
||||||
lastMessageReceived time.Time
|
lastMessageReceived time.Time
|
||||||
completedHandshake time.Time
|
completedHandshake time.Time
|
||||||
lastUsefulChunkReceived time.Time
|
lastUsefulChunkReceived time.Time
|
||||||
|
lastChunkSent time.Time
|
||||||
|
|
||||||
// Stuff controlled by the local peer.
|
// Stuff controlled by the local peer.
|
||||||
Interested bool
|
Interested bool
|
||||||
|
|
|
@ -170,6 +170,7 @@ func TestDownloadOnDemand(t *testing.T) {
|
||||||
DisableTrackers: true,
|
DisableTrackers: true,
|
||||||
NoDHT: true,
|
NoDHT: true,
|
||||||
ListenAddr: ":0",
|
ListenAddr: ":0",
|
||||||
|
Seed: true,
|
||||||
|
|
||||||
NoDefaultBlocklist: true,
|
NoDefaultBlocklist: true,
|
||||||
// Ensure that the metainfo is obtained over the wire, since we added
|
// Ensure that the metainfo is obtained over the wire, since we added
|
||||||
|
|
3
piece.go
3
piece.go
|
@ -20,7 +20,8 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type piece struct {
|
type piece struct {
|
||||||
Hash pieceSum // The completed piece SHA1 hash, from the metainfo "pieces" field.
|
// The completed piece SHA1 hash, from the metainfo "pieces" field.
|
||||||
|
Hash pieceSum
|
||||||
// Chunks we don't have. The offset and length can be determined by the
|
// Chunks we don't have. The offset and length can be determined by the
|
||||||
// request chunkSize in use.
|
// request chunkSize in use.
|
||||||
PendingChunkSpecs []bool
|
PendingChunkSpecs []bool
|
||||||
|
|
|
@ -131,10 +131,11 @@ func (t *torrent) addrActive(addr string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *torrent) worstConnsHeap() (wcs *worstConns) {
|
func (t *torrent) worstConnsHeap(cl *Client) (wcs *worstConns) {
|
||||||
wcs = &worstConns{
|
wcs = &worstConns{
|
||||||
c: append([]*connection{}, t.Conns...),
|
c: append([]*connection{}, t.Conns...),
|
||||||
t: t,
|
t: t,
|
||||||
|
cl: cl,
|
||||||
}
|
}
|
||||||
heap.Init(wcs)
|
heap.Init(wcs)
|
||||||
return
|
return
|
||||||
|
@ -376,7 +377,7 @@ func pieceStateRunStatusChars(psr PieceStateRun) (ret string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *torrent) writeStatus(w io.Writer) {
|
func (t *torrent) writeStatus(w io.Writer, cl *Client) {
|
||||||
fmt.Fprintf(w, "Infohash: %x\n", t.InfoHash)
|
fmt.Fprintf(w, "Infohash: %x\n", t.InfoHash)
|
||||||
fmt.Fprintf(w, "Metadata length: %d\n", t.metadataSize())
|
fmt.Fprintf(w, "Metadata length: %d\n", t.metadataSize())
|
||||||
fmt.Fprintf(w, "Metadata have: ")
|
fmt.Fprintf(w, "Metadata have: ")
|
||||||
|
@ -423,6 +424,7 @@ func (t *torrent) writeStatus(w io.Writer) {
|
||||||
sort.Sort(&worstConns{
|
sort.Sort(&worstConns{
|
||||||
c: t.Conns,
|
c: t.Conns,
|
||||||
t: t,
|
t: t,
|
||||||
|
cl: cl,
|
||||||
})
|
})
|
||||||
for _, c := range t.Conns {
|
for _, c := range t.Conns {
|
||||||
c.WriteStatus(w, t)
|
c.WriteStatus(w, t)
|
||||||
|
@ -685,8 +687,9 @@ func (t *torrent) wantChunk(r request) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *torrent) urgentChunkInPiece(piece int) bool {
|
func (t *torrent) urgentChunkInPiece(piece int) bool {
|
||||||
|
p := pp.Integer(piece)
|
||||||
for req := range t.urgent {
|
for req := range t.urgent {
|
||||||
if int(req.Index) == piece {
|
if req.Index == p {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,11 @@ import (
|
||||||
type worstConns struct {
|
type worstConns struct {
|
||||||
c []*connection
|
c []*connection
|
||||||
t *torrent
|
t *torrent
|
||||||
|
cl *Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (me worstConns) Len() int { return len(me.c) }
|
func (me *worstConns) Len() int { return len(me.c) }
|
||||||
func (me worstConns) Swap(i, j int) { me.c[i], me.c[j] = me.c[j], me.c[i] }
|
func (me *worstConns) Swap(i, j int) { me.c[i], me.c[j] = me.c[j], me.c[i] }
|
||||||
|
|
||||||
func (me *worstConns) Pop() (ret interface{}) {
|
func (me *worstConns) Pop() (ret interface{}) {
|
||||||
old := me.c
|
old := me.c
|
||||||
|
@ -26,37 +27,25 @@ func (me *worstConns) Push(x interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type worstConnsSortKey struct {
|
type worstConnsSortKey struct {
|
||||||
// Peer has something we want.
|
useful bool
|
||||||
useless bool
|
lastHelpful time.Time
|
||||||
// A fabricated duration since peer was last helpful.
|
|
||||||
age time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (me worstConnsSortKey) Less(other worstConnsSortKey) bool {
|
func (me worstConnsSortKey) Less(other worstConnsSortKey) bool {
|
||||||
if me.useless != other.useless {
|
if me.useful != other.useful {
|
||||||
return me.useless
|
return !me.useful
|
||||||
}
|
}
|
||||||
return me.age > other.age
|
return me.lastHelpful.Before(other.lastHelpful)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (me worstConns) key(i int) (key worstConnsSortKey) {
|
func (me *worstConns) key(i int) (key worstConnsSortKey) {
|
||||||
c := me.c[i]
|
c := me.c[i]
|
||||||
// Peer has had time to declare what they have.
|
key.useful = me.cl.usefulConn(me.t, c)
|
||||||
if time.Now().Sub(c.completedHandshake) >= 30*time.Second {
|
if me.cl.seeding(me.t) {
|
||||||
if !me.t.haveInfo() {
|
key.lastHelpful = c.lastChunkSent
|
||||||
key.useless = !c.supportsExtension("ut_metadata")
|
|
||||||
} else {
|
} else {
|
||||||
if !me.t.connHasWantedPieces(c) {
|
key.lastHelpful = c.lastUsefulChunkReceived
|
||||||
key.useless = true
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
key.age = time.Duration(1+3*c.UnwantedChunksReceived) * time.Now().Sub(func() time.Time {
|
|
||||||
if !c.lastUsefulChunkReceived.IsZero() {
|
|
||||||
return c.lastUsefulChunkReceived
|
|
||||||
}
|
|
||||||
return c.completedHandshake.Add(-time.Minute)
|
|
||||||
}()) / time.Duration(1+c.UsefulChunksReceived)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue