1 package request_strategy
8 "github.com/RoaringBitmap/roaring"
9 qt "github.com/frankban/quicktest"
10 "github.com/google/go-cmp/cmp"
14 gob.Register(chunkIterRange(0))
15 gob.Register(sliceChunksIter{})
18 type chunkIterRange ChunkIndex
20 func (me chunkIterRange) Iter(f func(ChunkIndex)) {
21 for offset := ChunkIndex(0); offset < ChunkIndex(me); offset += 1 {
26 type sliceChunksIter []ChunkIndex
28 func chunkIter(offsets ...ChunkIndex) ChunksIter {
29 return sliceChunksIter(offsets)
32 func (offsets sliceChunksIter) Iter(f func(ChunkIndex)) {
33 for _, offset := range offsets {
38 func requestSetFromSlice(rs ...RequestIndex) (ret roaring.Bitmap) {
44 gob.Register(intPeerId(0))
49 func (i intPeerId) Uintptr() uintptr {
53 var hasAllRequests = func() (all roaring.Bitmap) {
54 all.AddRange(0, roaring.MaxRange)
58 func TestStealingFromSlowerPeer(t *testing.T) {
61 MaxRequests: math.MaxInt16,
64 basePeer.Pieces.Add(0)
65 // Slower than the stealers, but has all requests already.
67 stealee.DownloadRate = 1
68 stealee.ExistingRequests = hasAllRequests
69 stealee.Id = intPeerId(1)
70 firstStealer := basePeer
71 firstStealer.Id = intPeerId(2)
72 secondStealer := basePeer
73 secondStealer.Id = intPeerId(3)
74 results := Run(Input{Torrents: []Torrent{{
79 IterPendingChunks: chunkIterRange(5),
88 c.Assert(results, qt.HasLen, 3)
89 check := func(p PeerId, l uint64) {
90 addressableBm := results[p].Requests
91 c.Check(addressableBm.GetCardinality(), qt.ContentEquals, l)
92 c.Check(results[p].Interested, qt.Equals, l > 0)
95 check(firstStealer.Id, 2)
96 check(secondStealer.Id, 2)
99 func checkNumRequestsAndInterest(c *qt.C, next PeerNextRequestState, num uint64, interest bool) {
100 addressableBm := next.Requests
101 c.Check(addressableBm.GetCardinality(), qt.ContentEquals, num)
102 c.Check(next.Interested, qt.Equals, interest)
105 func TestStealingFromSlowerPeersBasic(t *testing.T) {
108 MaxRequests: math.MaxInt16,
111 basePeer.Pieces.Add(0)
113 stealee.DownloadRate = 1
114 stealee.ExistingRequests = hasAllRequests
115 stealee.Id = intPeerId(1)
116 firstStealer := basePeer
117 firstStealer.Id = intPeerId(2)
118 secondStealer := basePeer
119 secondStealer.Id = intPeerId(3)
120 results := Run(Input{Torrents: []Torrent{{
125 IterPendingChunks: chunkIter(0, 1),
134 checkNumRequestsAndInterest(c, results[firstStealer.Id], 1, true)
135 checkNumRequestsAndInterest(c, results[secondStealer.Id], 1, true)
136 checkNumRequestsAndInterest(c, results[stealee.Id], 0, false)
139 func checkResultsRequestsLen(t *testing.T, reqs roaring.Bitmap, l uint64) {
140 qt.Check(t, reqs.GetCardinality(), qt.Equals, l)
143 func TestPeerKeepsExistingIfReasonable(t *testing.T) {
146 MaxRequests: math.MaxInt16,
149 basePeer.Pieces.Add(0)
150 // Slower than the stealers, but has all requests already.
152 stealee.DownloadRate = 1
153 keepReq := RequestIndex(0)
154 stealee.ExistingRequests = requestSetFromSlice(keepReq)
155 stealee.Id = intPeerId(1)
156 firstStealer := basePeer
157 firstStealer.Id = intPeerId(2)
158 secondStealer := basePeer
159 secondStealer.Id = intPeerId(3)
160 results := Run(Input{Torrents: []Torrent{{
165 IterPendingChunks: chunkIter(0, 1, 3, 4),
174 c.Assert(results, qt.HasLen, 3)
175 check := func(p PeerId, l uint64) {
176 checkResultsRequestsLen(t, results[p].Requests, l)
177 c.Check(results[p].Interested, qt.Equals, l > 0)
179 check(firstStealer.Id, 2)
180 check(secondStealer.Id, 1)
183 peerNextRequestStateChecker,
184 PeerNextRequestState{
186 Requests: requestSetFromSlice(keepReq),
191 var peerNextRequestStateChecker = qt.CmpEquals(
194 func(bm roaring.Bitmap) []uint32 {
198 func TestDontStealUnnecessarily(t *testing.T) {
201 MaxRequests: math.MaxInt16,
204 basePeer.Pieces.AddRange(0, 5)
205 // Slower than the stealers, but has all requests already.
207 stealee.DownloadRate = 1
208 r := func(i, c RequestIndex) RequestIndex {
211 keepReqs := requestSetFromSlice(
212 r(3, 2), r(3, 4), r(3, 6), r(3, 8),
213 r(4, 0), r(4, 1), r(4, 7), r(4, 8))
214 stealee.ExistingRequests = keepReqs
215 stealee.Id = intPeerId(1)
216 firstStealer := basePeer
217 firstStealer.Id = intPeerId(2)
218 secondStealer := basePeer
219 secondStealer.Id = intPeerId(3)
220 secondStealer.Pieces = roaring.Bitmap{}
221 secondStealer.Pieces.Add(1)
222 secondStealer.Pieces.Add(3)
223 results := Run(Input{Torrents: []Torrent{{
229 IterPendingChunks: chunkIterRange(9),
234 IterPendingChunks: chunkIterRange(7),
239 IterPendingChunks: chunkIterRange(0),
244 IterPendingChunks: chunkIterRange(9),
249 IterPendingChunks: chunkIterRange(9),
259 c.Assert(results, qt.HasLen, 3)
260 check := func(p PeerId, l uint64) {
261 checkResultsRequestsLen(t, results[p].Requests, l)
262 c.Check(results[p].Interested, qt.Equals, l > 0)
264 check(firstStealer.Id, 5)
265 check(secondStealer.Id, 7+9)
268 peerNextRequestStateChecker,
269 PeerNextRequestState{
271 Requests: requestSetFromSlice(r(4, 0), r(4, 1), r(4, 7), r(4, 8)),
276 // This tests a situation where multiple peers had the same existing request, due to "actual" and
277 // "next" request states being out of sync. This reasonable occurs when a peer hasn't fully updated
278 // its actual request state since the last request strategy run.
279 func TestDuplicatePreallocations(t *testing.T) {
280 peer := func(id int, downloadRate float64) Peer {
282 ExistingRequests: hasAllRequests,
285 DownloadRate: downloadRate,
287 p.Pieces.AddRange(0, roaring.MaxRange)
290 results := Run(Input{
291 Torrents: []Torrent{{
296 IterPendingChunks: chunkIterRange(1),
300 IterPendingChunks: chunkIterRange(1),
303 // The second peer was be marked as the preallocation, clobbering the first. The
304 // first peer is preferred, and the piece isn't striped, so it gets preallocated a
305 // request, and then gets reallocated from the peer the same request.
312 req1 := results[intPeerId(1)].Requests
313 req2 := results[intPeerId(2)].Requests
314 c.Assert(uint64(2), qt.Equals, req1.GetCardinality()+req2.GetCardinality())