Skip to content

Commit 528c5f6

Browse files
committed
Don't starve unverified bytes limit on unrequestable pieces
Solves issue described at #916 (comment).
2 parents 076036f + 0df3752 commit 528c5f6

File tree

14 files changed

+555
-18
lines changed

14 files changed

+555
-18
lines changed

request-strategy-impls.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,12 @@ type requestStrategyPiece struct {
8888
p *Piece
8989
}
9090

91-
func (r requestStrategyPiece) Request() bool {
92-
return !r.p.ignoreForRequests() && r.p.purePriority() != PiecePriorityNone
91+
func (r requestStrategyPiece) CountUnverified() bool {
92+
return r.p.hashing || r.p.marking || r.p.queuedForHash()
9393
}
9494

95-
func (r requestStrategyPiece) NumPendingChunks() int {
96-
return int(r.p.t.pieceNumPendingChunks(r.p.index))
95+
func (r requestStrategyPiece) Request() bool {
96+
return !r.p.ignoreForRequests() && r.p.purePriority() != PiecePriorityNone
9797
}
9898

99-
var _ request_strategy.Piece = (*requestStrategyPiece)(nil)
99+
var _ request_strategy.Piece = requestStrategyPiece{}

request-strategy/NOTES.md

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Rough notes on how requests are determined.
2+
3+
piece ordering cache:
4+
5+
- pieces are grouped by shared storage capacity and ordered by priority, availability, index and then infohash.
6+
- if a torrent does not have a storage cap, pieces are also filtered by whether requests should currently be made for them. this is because for torrents without a storage cap, there's no need to consider pieces that won't be requested.
7+
8+
building a list of candidate requests for a peer:
9+
10+
- pieces are scanned in order of the pre-sorted order for the storage group.
11+
- scanning stops when the cumulative piece length so far exceeds the storage capacity.
12+
- pieces are filtered by whether requests should currently be made for them (hashing, marking, already complete, etc.)
13+
- if requests were added to the consideration list, or the piece was in a partial state, the piece length is added to a cumulative total of unverified bytes.
14+
- if the cumulative total of unverified bytes reaches the configured limit (default 64MiB), piece scanning is halted.
15+
16+
applying request state:
17+
18+
- send the appropriate interest message if our interest doesn't match what the peer is seeing
19+
- sort all candidate requests by:
20+
- allowed fast if we're being choked,
21+
- piece priority,
22+
- whether the request is already outstanding to the peer,
23+
- whether the request is not pending from any peer
24+
- if the request is outstanding from a peer:
25+
- how many outstanding requests the existing peer has
26+
- most recently requested
27+
- least available piece

request-strategy/order.go

+13-9
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ func pieceOrderLess(i, j *pieceRequestOrderItem) multiless.Computation {
4242
// Calls f with requestable pieces in order.
4343
func GetRequestablePieces(
4444
input Input, pro *PieceRequestOrder,
45-
f func(ih metainfo.Hash, pieceIndex int, orderState PieceRequestOrderState),
45+
// Returns true if the piece should be considered against the unverified bytes limit.
46+
requestPiece func(ih metainfo.Hash, pieceIndex int, orderState PieceRequestOrderState) bool,
4647
) {
4748
// Storage capacity left for this run, keyed by the storage capacity pointer on the storage
4849
// TorrentImpl. A nil value means no capacity limit.
@@ -59,23 +60,26 @@ func GetRequestablePieces(
5960
var t = input.Torrent(ih)
6061
var piece = t.Piece(item.key.Index)
6162
pieceLength := t.PieceLength()
63+
// Storage limits will always apply against requestable pieces, since we need to keep the
64+
// highest priority pieces, even if they're complete or in an undesirable state.
6265
if storageLeft != nil {
6366
if *storageLeft < pieceLength {
6467
return false
6568
}
6669
*storageLeft -= pieceLength
6770
}
68-
if !piece.Request() || piece.NumPendingChunks() == 0 {
69-
// TODO: Clarify exactly what is verified. Stuff that's being hashed should be
70-
// considered unverified and hold up further requests.
71+
if piece.Request() {
72+
if !requestPiece(ih, item.key.Index, item.state) {
73+
// No blocks are being considered from this piece, so it won't result in unverified
74+
// bytes.
75+
return true
76+
}
77+
} else if !piece.CountUnverified() {
78+
// The piece is pristine, and we're not considering it for requests.
7179
return true
7280
}
73-
if maxUnverifiedBytes != 0 && allTorrentsUnverifiedBytes+pieceLength > maxUnverifiedBytes {
74-
return false
75-
}
7681
allTorrentsUnverifiedBytes += pieceLength
77-
f(ih, item.key.Index, item.state)
78-
return true
82+
return maxUnverifiedBytes == 0 || allTorrentsUnverifiedBytes < maxUnverifiedBytes
7983
})
8084
return
8185
}

request-strategy/piece.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package requestStrategy
22

33
type Piece interface {
4+
// Whether requests should be made for this piece. This would be false for cases like the piece
5+
// is currently being hashed, or already complete.
46
Request() bool
5-
NumPendingChunks() int
7+
// Whether the piece should be counted towards the unverified bytes limit. The intention is to
8+
// prevent pieces being starved from the opportunity to move to the completed state.
9+
CountUnverified() bool
610
}

requesting.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,12 @@ func (p *Peer) getDesiredRequestState() (desired desiredRequestState) {
191191
requestStrategy.GetRequestablePieces(
192192
input,
193193
t.getPieceRequestOrder(),
194-
func(ih InfoHash, pieceIndex int, pieceExtra requestStrategy.PieceRequestOrderState) {
194+
func(ih InfoHash, pieceIndex int, pieceExtra requestStrategy.PieceRequestOrderState) bool {
195195
if ih != *t.canonicalShortInfohash() {
196-
return
196+
return false
197197
}
198198
if !p.peerHasPiece(pieceIndex) {
199-
return
199+
return false
200200
}
201201
requestHeap.pieceStates[pieceIndex].Set(pieceExtra)
202202
allowedFast := p.peerAllowedFast.Contains(pieceIndex)
@@ -222,6 +222,7 @@ func (p *Peer) getDesiredRequestState() (desired desiredRequestState) {
222222
}
223223
requestHeap.requestIndexes = append(requestHeap.requestIndexes, r)
224224
})
225+
return true
225226
},
226227
)
227228
t.assertPendingRequests()

tests/README

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This directory contains integration style tests that probably run too long for regular unit testing. It's a separate module to avoid getting run by ./... in the root.

tests/go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/anacrolix/torrent/tests
2+
3+
go 1.22.0

tests/go.work

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
go 1.22.0
2+
3+
use (
4+
.
5+
..
6+
)

0 commit comments

Comments
 (0)