2021-05-13 09:56:58 +10:00
|
|
|
package request_strategy
|
2021-05-13 11:26:22 +10:00
|
|
|
|
|
|
|
import (
|
|
|
|
"math"
|
|
|
|
"testing"
|
|
|
|
|
2021-09-19 15:16:37 +10:00
|
|
|
"github.com/RoaringBitmap/roaring"
|
2021-05-13 11:26:22 +10:00
|
|
|
qt "github.com/frankban/quicktest"
|
2021-09-19 15:16:37 +10:00
|
|
|
"github.com/google/go-cmp/cmp"
|
2021-05-13 11:26:22 +10:00
|
|
|
)
|
|
|
|
|
2021-09-19 15:16:37 +10:00
|
|
|
func chunkIterRange(end ChunkIndex) ChunksIter {
|
|
|
|
return func(f func(ChunkIndex)) {
|
|
|
|
for offset := ChunkIndex(0); offset < end; offset += 1 {
|
|
|
|
f(offset)
|
2021-05-13 20:56:12 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-19 15:16:37 +10:00
|
|
|
func chunkIter(offsets ...ChunkIndex) ChunksIter {
|
|
|
|
return func(f func(ChunkIndex)) {
|
2021-05-13 11:26:22 +10:00
|
|
|
for _, offset := range offsets {
|
2021-09-19 15:16:37 +10:00
|
|
|
f(offset)
|
2021-05-13 11:26:22 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-19 15:16:37 +10:00
|
|
|
func requestSetFromSlice(rs ...RequestIndex) (ret roaring.Bitmap) {
|
|
|
|
ret.AddMany(rs)
|
2021-05-13 11:26:22 +10:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
type intPeerId int
|
|
|
|
|
|
|
|
func (i intPeerId) Uintptr() uintptr {
|
|
|
|
return uintptr(i)
|
|
|
|
}
|
|
|
|
|
2021-09-19 15:16:37 +10:00
|
|
|
func hasAllRequests(RequestIndex) bool { return true }
|
|
|
|
|
2021-05-13 13:50:41 +10:00
|
|
|
func TestStealingFromSlowerPeer(t *testing.T) {
|
|
|
|
c := qt.New(t)
|
|
|
|
basePeer := Peer{
|
|
|
|
HasPiece: func(i pieceIndex) bool {
|
|
|
|
return true
|
|
|
|
},
|
|
|
|
MaxRequests: math.MaxInt16,
|
|
|
|
DownloadRate: 2,
|
|
|
|
}
|
|
|
|
// Slower than the stealers, but has all requests already.
|
|
|
|
stealee := basePeer
|
|
|
|
stealee.DownloadRate = 1
|
2021-09-19 15:16:37 +10:00
|
|
|
stealee.HasExistingRequest = hasAllRequests
|
2021-05-13 13:50:41 +10:00
|
|
|
stealee.Id = intPeerId(1)
|
|
|
|
firstStealer := basePeer
|
|
|
|
firstStealer.Id = intPeerId(2)
|
|
|
|
secondStealer := basePeer
|
|
|
|
secondStealer.Id = intPeerId(3)
|
2021-05-14 13:40:09 +10:00
|
|
|
results := Run(Input{Torrents: []Torrent{{
|
2021-09-19 15:16:37 +10:00
|
|
|
ChunksPerPiece: 9,
|
2021-05-13 13:50:41 +10:00
|
|
|
Pieces: []Piece{{
|
|
|
|
Request: true,
|
|
|
|
NumPendingChunks: 5,
|
2021-05-13 20:56:12 +10:00
|
|
|
IterPendingChunks: chunkIterRange(5),
|
2021-05-13 13:50:41 +10:00
|
|
|
}},
|
|
|
|
Peers: []Peer{
|
|
|
|
stealee,
|
|
|
|
firstStealer,
|
|
|
|
secondStealer,
|
|
|
|
},
|
2021-05-14 13:40:09 +10:00
|
|
|
}}})
|
|
|
|
|
2021-05-13 13:50:41 +10:00
|
|
|
c.Assert(results, qt.HasLen, 3)
|
2021-09-19 15:16:37 +10:00
|
|
|
check := func(p PeerId, l uint64) {
|
|
|
|
addressableBm := results[p].Requests
|
|
|
|
c.Check(addressableBm.GetCardinality(), qt.ContentEquals, l)
|
2021-05-13 13:50:41 +10:00
|
|
|
c.Check(results[p].Interested, qt.Equals, l > 0)
|
|
|
|
}
|
|
|
|
check(stealee.Id, 1)
|
|
|
|
check(firstStealer.Id, 2)
|
|
|
|
check(secondStealer.Id, 2)
|
|
|
|
}
|
|
|
|
|
2021-09-19 15:16:37 +10:00
|
|
|
func checkNumRequestsAndInterest(c *qt.C, next PeerNextRequestState, num uint64, interest bool) {
|
|
|
|
addressableBm := next.Requests
|
|
|
|
c.Check(addressableBm.GetCardinality(), qt.ContentEquals, num)
|
2021-05-14 10:23:18 +10:00
|
|
|
c.Check(next.Interested, qt.Equals, interest)
|
|
|
|
}
|
|
|
|
|
2021-05-13 13:50:41 +10:00
|
|
|
func TestStealingFromSlowerPeersBasic(t *testing.T) {
|
2021-05-13 11:26:22 +10:00
|
|
|
c := qt.New(t)
|
|
|
|
basePeer := Peer{
|
|
|
|
HasPiece: func(i pieceIndex) bool {
|
|
|
|
return true
|
|
|
|
},
|
|
|
|
MaxRequests: math.MaxInt16,
|
|
|
|
DownloadRate: 2,
|
|
|
|
}
|
|
|
|
stealee := basePeer
|
|
|
|
stealee.DownloadRate = 1
|
2021-09-19 15:16:37 +10:00
|
|
|
stealee.HasExistingRequest = hasAllRequests
|
2021-05-13 11:26:22 +10:00
|
|
|
stealee.Id = intPeerId(1)
|
|
|
|
firstStealer := basePeer
|
|
|
|
firstStealer.Id = intPeerId(2)
|
|
|
|
secondStealer := basePeer
|
|
|
|
secondStealer.Id = intPeerId(3)
|
2021-05-14 13:40:09 +10:00
|
|
|
results := Run(Input{Torrents: []Torrent{{
|
2021-09-19 15:16:37 +10:00
|
|
|
ChunksPerPiece: 9,
|
2021-05-13 11:26:22 +10:00
|
|
|
Pieces: []Piece{{
|
|
|
|
Request: true,
|
|
|
|
NumPendingChunks: 2,
|
|
|
|
IterPendingChunks: chunkIter(0, 1),
|
|
|
|
}},
|
|
|
|
Peers: []Peer{
|
|
|
|
stealee,
|
|
|
|
firstStealer,
|
|
|
|
secondStealer,
|
|
|
|
},
|
2021-05-14 13:40:09 +10:00
|
|
|
}}})
|
|
|
|
|
2021-05-14 10:23:18 +10:00
|
|
|
checkNumRequestsAndInterest(c, results[firstStealer.Id], 1, true)
|
|
|
|
checkNumRequestsAndInterest(c, results[secondStealer.Id], 1, true)
|
|
|
|
checkNumRequestsAndInterest(c, results[stealee.Id], 0, false)
|
2021-05-13 11:26:22 +10:00
|
|
|
}
|
2021-05-13 13:50:41 +10:00
|
|
|
|
2021-09-19 15:16:37 +10:00
|
|
|
func checkResultsRequestsLen(t *testing.T, reqs roaring.Bitmap, l uint64) {
|
|
|
|
qt.Check(t, reqs.GetCardinality(), qt.Equals, l)
|
|
|
|
}
|
|
|
|
|
2021-05-13 13:50:41 +10:00
|
|
|
func TestPeerKeepsExistingIfReasonable(t *testing.T) {
|
|
|
|
c := qt.New(t)
|
|
|
|
basePeer := Peer{
|
|
|
|
HasPiece: func(i pieceIndex) bool {
|
|
|
|
return true
|
|
|
|
},
|
|
|
|
MaxRequests: math.MaxInt16,
|
|
|
|
DownloadRate: 2,
|
|
|
|
}
|
|
|
|
// Slower than the stealers, but has all requests already.
|
|
|
|
stealee := basePeer
|
|
|
|
stealee.DownloadRate = 1
|
2021-09-19 15:16:37 +10:00
|
|
|
keepReq := RequestIndex(0)
|
|
|
|
stealee.HasExistingRequest = func(r RequestIndex) bool {
|
2021-05-13 13:50:41 +10:00
|
|
|
return r == keepReq
|
|
|
|
}
|
|
|
|
stealee.Id = intPeerId(1)
|
|
|
|
firstStealer := basePeer
|
|
|
|
firstStealer.Id = intPeerId(2)
|
|
|
|
secondStealer := basePeer
|
|
|
|
secondStealer.Id = intPeerId(3)
|
2021-05-14 13:40:09 +10:00
|
|
|
results := Run(Input{Torrents: []Torrent{{
|
2021-09-19 15:16:37 +10:00
|
|
|
ChunksPerPiece: 9,
|
2021-05-13 13:50:41 +10:00
|
|
|
Pieces: []Piece{{
|
|
|
|
Request: true,
|
|
|
|
NumPendingChunks: 4,
|
|
|
|
IterPendingChunks: chunkIter(0, 1, 3, 4),
|
|
|
|
}},
|
|
|
|
Peers: []Peer{
|
|
|
|
stealee,
|
|
|
|
firstStealer,
|
|
|
|
secondStealer,
|
|
|
|
},
|
2021-05-14 13:40:09 +10:00
|
|
|
}}})
|
|
|
|
|
2021-05-13 13:50:41 +10:00
|
|
|
c.Assert(results, qt.HasLen, 3)
|
2021-09-19 15:16:37 +10:00
|
|
|
check := func(p PeerId, l uint64) {
|
|
|
|
checkResultsRequestsLen(t, results[p].Requests, l)
|
2021-05-13 13:50:41 +10:00
|
|
|
c.Check(results[p].Interested, qt.Equals, l > 0)
|
|
|
|
}
|
|
|
|
check(firstStealer.Id, 2)
|
|
|
|
check(secondStealer.Id, 1)
|
2021-09-19 15:16:37 +10:00
|
|
|
c.Check(
|
|
|
|
results[stealee.Id],
|
|
|
|
peerNextRequestStateChecker,
|
|
|
|
PeerNextRequestState{
|
|
|
|
Interested: true,
|
|
|
|
Requests: requestSetFromSlice(keepReq),
|
|
|
|
},
|
|
|
|
)
|
2021-05-13 13:50:41 +10:00
|
|
|
}
|
2021-05-13 20:56:12 +10:00
|
|
|
|
2021-09-19 15:16:37 +10:00
|
|
|
var peerNextRequestStateChecker = qt.CmpEquals(
|
|
|
|
cmp.Transformer(
|
|
|
|
"bitmap",
|
|
|
|
func(bm roaring.Bitmap) []uint32 {
|
|
|
|
return bm.ToArray()
|
|
|
|
}))
|
|
|
|
|
2021-05-13 20:56:12 +10:00
|
|
|
func TestDontStealUnnecessarily(t *testing.T) {
|
|
|
|
c := qt.New(t)
|
|
|
|
basePeer := Peer{
|
|
|
|
HasPiece: func(i pieceIndex) bool {
|
|
|
|
return true
|
|
|
|
},
|
|
|
|
MaxRequests: math.MaxInt16,
|
|
|
|
DownloadRate: 2,
|
|
|
|
}
|
|
|
|
// Slower than the stealers, but has all requests already.
|
|
|
|
stealee := basePeer
|
|
|
|
stealee.DownloadRate = 1
|
2021-09-19 15:16:37 +10:00
|
|
|
r := func(i, c RequestIndex) RequestIndex {
|
|
|
|
return i*9 + c
|
|
|
|
}
|
2021-05-20 11:26:08 +10:00
|
|
|
keepReqs := requestSetFromSlice(
|
|
|
|
r(3, 2), r(3, 4), r(3, 6), r(3, 8),
|
|
|
|
r(4, 0), r(4, 1), r(4, 7), r(4, 8))
|
2021-09-19 15:16:37 +10:00
|
|
|
stealee.HasExistingRequest = func(r RequestIndex) bool {
|
|
|
|
return keepReqs.Contains(r)
|
2021-05-13 20:56:12 +10:00
|
|
|
}
|
|
|
|
stealee.Id = intPeerId(1)
|
|
|
|
firstStealer := basePeer
|
|
|
|
firstStealer.Id = intPeerId(2)
|
|
|
|
secondStealer := basePeer
|
|
|
|
secondStealer.Id = intPeerId(3)
|
2021-05-20 11:26:08 +10:00
|
|
|
secondStealer.HasPiece = func(i pieceIndex) bool {
|
|
|
|
switch i {
|
|
|
|
case 1, 3:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
2021-05-14 13:40:09 +10:00
|
|
|
results := Run(Input{Torrents: []Torrent{{
|
2021-09-19 15:16:37 +10:00
|
|
|
ChunksPerPiece: 9,
|
2021-05-20 11:26:08 +10:00
|
|
|
Pieces: []Piece{
|
|
|
|
{
|
|
|
|
Request: true,
|
|
|
|
NumPendingChunks: 0,
|
|
|
|
IterPendingChunks: chunkIterRange(9),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Request: true,
|
|
|
|
NumPendingChunks: 7,
|
|
|
|
IterPendingChunks: chunkIterRange(7),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Request: true,
|
|
|
|
NumPendingChunks: 0,
|
|
|
|
IterPendingChunks: chunkIterRange(0),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Request: true,
|
|
|
|
NumPendingChunks: 9,
|
|
|
|
IterPendingChunks: chunkIterRange(9),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Request: true,
|
|
|
|
NumPendingChunks: 9,
|
|
|
|
IterPendingChunks: chunkIterRange(9),
|
|
|
|
}},
|
2021-05-13 20:56:12 +10:00
|
|
|
Peers: []Peer{
|
|
|
|
firstStealer,
|
|
|
|
stealee,
|
|
|
|
secondStealer,
|
|
|
|
},
|
2021-05-14 13:40:09 +10:00
|
|
|
}}})
|
|
|
|
|
2021-05-13 20:56:12 +10:00
|
|
|
c.Assert(results, qt.HasLen, 3)
|
2021-09-19 15:16:37 +10:00
|
|
|
check := func(p PeerId, l uint64) {
|
|
|
|
checkResultsRequestsLen(t, results[p].Requests, l)
|
2021-05-13 20:56:12 +10:00
|
|
|
c.Check(results[p].Interested, qt.Equals, l > 0)
|
|
|
|
}
|
2021-05-20 11:26:08 +10:00
|
|
|
check(firstStealer.Id, 5)
|
|
|
|
check(secondStealer.Id, 7+9)
|
2021-09-19 15:16:37 +10:00
|
|
|
c.Check(
|
|
|
|
results[stealee.Id],
|
|
|
|
peerNextRequestStateChecker,
|
|
|
|
PeerNextRequestState{
|
|
|
|
Interested: true,
|
|
|
|
Requests: requestSetFromSlice(r(4, 0), r(4, 1), r(4, 7), r(4, 8)),
|
|
|
|
},
|
|
|
|
)
|
2021-05-13 20:56:12 +10:00
|
|
|
}
|
2021-05-21 14:02:45 +10:00
|
|
|
|
|
|
|
// This tests a situation where multiple peers had the same existing request, due to "actual" and
|
|
|
|
// "next" request states being out of sync. This reasonable occurs when a peer hasn't fully updated
|
|
|
|
// its actual request state since the last request strategy run.
|
|
|
|
func TestDuplicatePreallocations(t *testing.T) {
|
|
|
|
peer := func(id int, downloadRate float64) Peer {
|
|
|
|
return Peer{
|
2021-09-19 15:16:37 +10:00
|
|
|
HasExistingRequest: hasAllRequests,
|
|
|
|
MaxRequests: 2,
|
2021-05-21 14:02:45 +10:00
|
|
|
HasPiece: func(i pieceIndex) bool {
|
|
|
|
return true
|
|
|
|
},
|
|
|
|
Id: intPeerId(id),
|
|
|
|
DownloadRate: downloadRate,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
results := Run(Input{
|
|
|
|
Torrents: []Torrent{{
|
2021-09-19 15:16:37 +10:00
|
|
|
ChunksPerPiece: 1,
|
2021-05-21 14:02:45 +10:00
|
|
|
Pieces: []Piece{{
|
|
|
|
Request: true,
|
|
|
|
NumPendingChunks: 1,
|
|
|
|
IterPendingChunks: chunkIterRange(1),
|
|
|
|
}, {
|
|
|
|
Request: true,
|
|
|
|
NumPendingChunks: 1,
|
|
|
|
IterPendingChunks: chunkIterRange(1),
|
|
|
|
}},
|
|
|
|
Peers: []Peer{
|
|
|
|
// The second peer was be marked as the preallocation, clobbering the first. The
|
|
|
|
// first peer is preferred, and the piece isn't striped, so it gets preallocated a
|
|
|
|
// request, and then gets reallocated from the peer the same request.
|
|
|
|
peer(1, 2),
|
|
|
|
peer(2, 1),
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
})
|
|
|
|
c := qt.New(t)
|
2021-09-19 15:16:37 +10:00
|
|
|
req1 := results[intPeerId(1)].Requests
|
|
|
|
req2 := results[intPeerId(2)].Requests
|
|
|
|
c.Assert(uint64(2), qt.Equals, req1.GetCardinality()+req2.GetCardinality())
|
2021-05-21 14:02:45 +10:00
|
|
|
}
|