8 "github.com/anacrolix/missinggo/v2/bitmap"
9 "github.com/anacrolix/missinggo/v2/prioritybitmap"
11 pp "github.com/anacrolix/torrent/peer_protocol"
14 type requestStrategyPiece interface {
15 numChunks() pp.Integer
16 dirtyChunks() bitmap.Bitmap
17 chunkIndexRequest(i pp.Integer) request
20 type requestStrategyTorrent interface {
24 readerPiecePriorities() (now, readahead bitmap.Bitmap)
25 ignorePieces() bitmap.Bitmap
26 pendingPieces() *prioritybitmap.PriorityBitmap
29 type requestStrategyConnection interface {
30 torrent() requestStrategyTorrent
31 peerPieces() bitmap.Bitmap
32 pieceRequestOrder() *prioritybitmap.PriorityBitmap
35 totalExpectingTime() time.Duration
37 chunksReceivedWhileExpecting() int64
40 type requestStrategy interface {
41 iterPendingPieces(requestStrategyConnection, func(pieceIndex) bool) bool
42 iterUndirtiedChunks(requestStrategyPiece, func(chunkSpec) bool) bool
43 nominalMaxRequests(requestStrategyConnection) int
44 shouldRequestWithoutBias(requestStrategyConnection) bool
45 piecePriority(requestStrategyConnection, pieceIndex, piecePriority, int) int
46 hooks() requestStrategyHooks
49 type requestStrategyHooks struct {
50 sentRequest func(request)
51 deletedRequest func(request)
54 type requestStrategyCallbacks interface {
55 requestTimedOut(request)
58 type requestStrategyFuzzing struct {
59 requestStrategyDefaults
62 type requestStrategyFastest struct {
63 requestStrategyDefaults
66 func newRequestStrategyMaker(rs requestStrategy) requestStrategyMaker {
67 return func(requestStrategyCallbacks, sync.Locker) requestStrategy {
72 // The fastest connection downloads strictly in order of priority, while all others adhere to their
73 // piece inclinations.
74 func RequestStrategyFastest() requestStrategyMaker {
75 return newRequestStrategyMaker(requestStrategyFastest{})
78 // Favour higher priority pieces with some fuzzing to reduce overlaps and wastage across
80 func RequestStrategyFuzzing() requestStrategyMaker {
81 return newRequestStrategyMaker(requestStrategyFuzzing{})
84 func (requestStrategyFastest) shouldRequestWithoutBias(cn requestStrategyConnection) bool {
85 if cn.torrent().numReaders() == 0 {
88 if cn.torrent().numConns() == 1 {
97 type requestStrategyDuplicateRequestTimeout struct {
98 requestStrategyDefaults
99 // How long to avoid duplicating a pending request.
100 duplicateRequestTimeout time.Duration
102 callbacks requestStrategyCallbacks
104 // The last time we requested a chunk. Deleting the request from any connection will clear this
106 lastRequested map[request]*time.Timer
107 // The lock to take when running a request timeout handler.
108 timeoutLocker sync.Locker
111 // Generates a request strategy instance for a given torrent. callbacks are probably specific to the torrent.
112 type requestStrategyMaker func(callbacks requestStrategyCallbacks, clientLocker sync.Locker) requestStrategy
114 // Requests are strictly by piece priority, and not duplicated until duplicateRequestTimeout is
116 func RequestStrategyDuplicateRequestTimeout(duplicateRequestTimeout time.Duration) requestStrategyMaker {
117 return func(callbacks requestStrategyCallbacks, clientLocker sync.Locker) requestStrategy {
118 return requestStrategyDuplicateRequestTimeout{
119 duplicateRequestTimeout: duplicateRequestTimeout,
120 callbacks: callbacks,
121 lastRequested: make(map[request]*time.Timer),
122 timeoutLocker: clientLocker,
127 func (rs requestStrategyDuplicateRequestTimeout) hooks() requestStrategyHooks {
128 return requestStrategyHooks{
129 deletedRequest: func(r request) {
130 if t, ok := rs.lastRequested[r]; ok {
132 delete(rs.lastRequested, r)
135 sentRequest: rs.onSentRequest,
139 func (rs requestStrategyDuplicateRequestTimeout) iterUndirtiedChunks(p requestStrategyPiece, f func(chunkSpec) bool) bool {
140 for i := pp.Integer(0); i < pp.Integer(p.numChunks()); i++ {
141 if p.dirtyChunks().Get(bitmap.BitIndex(i)) {
144 r := p.chunkIndexRequest(i)
145 if rs.wouldDuplicateRecent(r) {
155 func (requestStrategyFuzzing) piecePriority(cn requestStrategyConnection, piece pieceIndex, tpp piecePriority, prio int) int {
157 case PiecePriorityNormal:
158 case PiecePriorityReadahead:
159 prio -= int(cn.torrent().numPieces())
160 case PiecePriorityNext, PiecePriorityNow:
161 prio -= 2 * int(cn.torrent().numPieces())
165 prio += int(piece / 3)
169 func (requestStrategyDuplicateRequestTimeout) iterPendingPieces(cn requestStrategyConnection, f func(pieceIndex) bool) bool {
170 return iterUnbiasedPieceRequestOrder(cn, f)
172 func defaultIterPendingPieces(rs requestStrategy, cn requestStrategyConnection, f func(pieceIndex) bool) bool {
173 if rs.shouldRequestWithoutBias(cn) {
174 return iterUnbiasedPieceRequestOrder(cn, f)
176 return cn.pieceRequestOrder().IterTyped(func(i int) bool {
177 return f(pieceIndex(i))
181 func (rs requestStrategyFuzzing) iterPendingPieces(cn requestStrategyConnection, cb func(pieceIndex) bool) bool {
182 return defaultIterPendingPieces(rs, cn, cb)
184 func (rs requestStrategyFastest) iterPendingPieces(cn requestStrategyConnection, cb func(pieceIndex) bool) bool {
185 return defaultIterPendingPieces(rs, cn, cb)
188 func (rs requestStrategyDuplicateRequestTimeout) onSentRequest(r request) {
189 rs.lastRequested[r] = time.AfterFunc(rs.duplicateRequestTimeout, func() {
190 rs.timeoutLocker.Lock()
191 delete(rs.lastRequested, r)
192 rs.timeoutLocker.Unlock()
193 rs.callbacks.requestTimedOut(r)
197 // The actual value to use as the maximum outbound requests.
198 func (rs requestStrategyDuplicateRequestTimeout) nominalMaxRequests(cn requestStrategyConnection) (ret int) {
199 expectingTime := int64(cn.totalExpectingTime())
200 if expectingTime == 0 {
201 expectingTime = math.MaxInt64
207 int64(cn.peerMaxRequests()),
209 // It makes sense to always pipeline at least one connection, since latency must be
212 // Request only as many as we expect to receive in the duplicateRequestTimeout
213 // window. We are trying to avoid having to duplicate requests.
214 cn.chunksReceivedWhileExpecting()*int64(rs.duplicateRequestTimeout)/expectingTime,
218 func (rs requestStrategyDuplicateRequestTimeout) wouldDuplicateRecent(r request) bool {
219 // This piece has been requested on another connection, and the duplicate request timer is still
221 _, ok := rs.lastRequested[r]