File indexing completed on 2025-02-16 04:37:32
0001 /* 0002 SPDX-FileCopyrightText: 2005 Joris Guisson <joris.guisson@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 #include "chunkselector.h" 0007 #include "chunkdownload.h" 0008 #include "downloader.h" 0009 #include <algorithm> 0010 #include <random> 0011 #include <diskio/chunkmanager.h> 0012 #include <interfaces/piecedownloader.h> 0013 #include <peer/chunkcounter.h> 0014 #include <peer/peer.h> 0015 #include <peer/peermanager.h> 0016 #include <stdlib.h> 0017 #include <torrent/torrent.h> 0018 #include <util/bitset.h> 0019 #include <util/log.h> 0020 #include <vector> 0021 0022 namespace bt 0023 { 0024 struct RareCmp { 0025 ChunkManager *cman; 0026 ChunkCounter &cc; 0027 bool warmup; 0028 0029 RareCmp(ChunkManager *cman, ChunkCounter &cc, bool warmup) 0030 : cman(cman) 0031 , cc(cc) 0032 , warmup(warmup) 0033 { 0034 } 0035 0036 bool operator()(Uint32 a, Uint32 b) 0037 { 0038 if (a >= cman->getNumChunks() || b >= cman->getNumChunks()) 0039 return false; 0040 // the sorting is done on two criteria, priority and rareness 0041 Priority pa = cman->getChunk(a)->getPriority(); 0042 Priority pb = cman->getChunk(b)->getPriority(); 0043 if (pa == pb) 0044 return normalCmp(a, b); // if both have same priority compare on rareness 0045 else if (pa > pb) // pa has priority over pb, so select pa 0046 return true; 0047 else // pb has priority over pa, so select pb 0048 return false; 0049 } 0050 0051 bool normalCmp(Uint32 a, Uint32 b) 0052 { 0053 // during warmup mode choose most common chunks 0054 if (!warmup) 0055 return cc.get(a) < cc.get(b); 0056 else 0057 return cc.get(a) > cc.get(b); 0058 } 0059 }; 0060 0061 ChunkSelector::ChunkSelector() 0062 { 0063 } 0064 0065 ChunkSelector::~ChunkSelector() 0066 { 0067 } 0068 0069 void ChunkSelector::init(ChunkManager *cman, Downloader *downer, PeerManager *pman) 0070 { 0071 bt::ChunkSelectorInterface::init(cman, downer, pman); 0072 std::vector<Uint32> tmp; 0073 std::random_device rd; 0074 std::mt19937 g(rd()); 0075 0076 for (Uint32 i = 0; i < cman->getNumChunks(); i++) { 0077 if (!cman->getBitSet().get(i)) { 0078 tmp.push_back(i); 0079 } 0080 } 0081 std::shuffle(tmp.begin(),tmp.end(), g); 0082 // std::list does not support random_shuffle so we use a vector as a temporary storage 0083 // for the random_shuffle 0084 chunks.insert(chunks.begin(), tmp.begin(), tmp.end()); 0085 sort_timer.update(); 0086 } 0087 0088 Uint32 ChunkSelector::leastPeers(const std::list<Uint32> &lp, Uint32 alternative, Uint32 max_peers_per_chunk) 0089 { 0090 bool endgame = downer->endgameMode(); 0091 0092 Uint32 sel = lp.front(); 0093 Uint32 cnt = downer->numDownloadersForChunk(sel); 0094 for (Uint32 i : lp) { 0095 Uint32 cnt_i = downer->numDownloadersForChunk(i); 0096 if (cnt_i < cnt) { 0097 sel = i; 0098 cnt = cnt_i; 0099 } 0100 } 0101 0102 if (!endgame && downer->numDownloadersForChunk(sel) >= max_peers_per_chunk) { 0103 ChunkDownload *cd = downer->download(sel); 0104 if (!cd) 0105 return alternative; 0106 0107 // if download speed is very small, use sel 0108 // even though the max_peers_per_chunk has been reached 0109 if (cd->getDownloadSpeed() < 100) 0110 return sel; 0111 else 0112 return alternative; 0113 } 0114 0115 return sel; 0116 } 0117 0118 bool ChunkSelector::select(PieceDownloader *pd, Uint32 &chunk) 0119 { 0120 const BitSet &bs = cman->getBitSet(); 0121 0122 Uint32 sel = ~Uint32(); 0123 Uint32 sel_dl = ~Uint32(); 0124 Priority sel_prio = EXCLUDED; 0125 0126 // sort the chunks every 2 seconds 0127 if (sort_timer.getElapsedSinceUpdate() > 2000) { 0128 bool warmup = cman->getNumChunks() - cman->chunksLeft() <= 4; 0129 chunks.sort(RareCmp(cman, pman->getChunkCounter(), warmup)); 0130 sort_timer.update(); 0131 } 0132 0133 std::list<Uint32>::iterator itr = chunks.begin(); 0134 while (itr != chunks.end()) { 0135 Uint32 i = *itr; 0136 Chunk *c = cman->getChunk(*itr); 0137 0138 // if we have the chunk remove it from the list 0139 if (c->isExcludedForDownloading() || c->isExcluded() || bs.get(i)) { 0140 std::list<Uint32>::iterator tmp = itr; 0141 ++itr; 0142 chunks.erase(tmp); 0143 } else if (c->getPriority() < sel_prio) { 0144 // we've already found a suitable chunk at a higher priority, so select that one 0145 break; 0146 } else if (pd->hasChunk(i)) { 0147 // pd has to have the selected chunk and it needs to be not excluded 0148 Uint32 dl = downer->numDownloadersForChunk(i); 0149 if (dl == 0) { 0150 // we found a chunk that has no downloaders, so select it 0151 sel = i; 0152 break; 0153 } 0154 // we found a chunk that has downloaders; remember it if it has fewer 0155 // downloaders than the chunk we're already remembering (if any) and: 0156 // - it has fewer than max_peers_per_chunk downloaders, or 0157 // - we're in endgame mode, or 0158 // - it is downloading very slowly 0159 if (dl < sel_dl) { 0160 const Uint32 max_peers_per_chunk = c->isPreview() ? 3 : 2; 0161 ChunkDownload *cd; 0162 if (dl < max_peers_per_chunk || downer->endgameMode() || 0163 ((cd = downer->download(i)) && cd->getDownloadSpeed() < 100)) 0164 { 0165 sel = i; 0166 sel_dl = dl; 0167 sel_prio = c->getPriority(); 0168 } 0169 } 0170 ++itr; 0171 } else 0172 ++itr; 0173 } 0174 0175 if (sel >= cman->getNumChunks()) 0176 return false; 0177 0178 chunk = sel; 0179 return true; 0180 } 0181 0182 void ChunkSelector::dataChecked(const BitSet &ok_chunks, Uint32 from, Uint32 to) 0183 { 0184 for (Uint32 i = from; i < ok_chunks.getNumBits() && i <= to; i++) { 0185 bool in_chunks = std::find(chunks.begin(), chunks.end(), i) != chunks.end(); 0186 if (in_chunks && ok_chunks.get(i)) { 0187 // if we have the chunk, remove it from the chunks list 0188 chunks.remove(i); 0189 } else if (!in_chunks && !ok_chunks.get(i)) { 0190 // if we don't have the chunk, add it to the list if it wasn't allrready in there 0191 chunks.push_back(i); 0192 } 0193 } 0194 } 0195 0196 void ChunkSelector::reincluded(Uint32 from, Uint32 to) 0197 { 0198 // lets do a safety check first 0199 if (from >= cman->getNumChunks() || to >= cman->getNumChunks()) { 0200 Out(SYS_DIO | LOG_NOTICE) << "Internal error in chunkselector" << endl; 0201 return; 0202 } 0203 0204 for (Uint32 i = from; i <= to; i++) { 0205 bool in_chunks = std::find(chunks.begin(), chunks.end(), i) != chunks.end(); 0206 if (!in_chunks && cman->getChunk(i)->getStatus() != Chunk::ON_DISK) { 0207 // Out(SYS_DIO|LOG_DEBUG) << "ChunkSelector::reIncluded " << i << endl; 0208 chunks.push_back(i); 0209 } 0210 } 0211 } 0212 0213 void ChunkSelector::reinsert(Uint32 chunk) 0214 { 0215 bool in_chunks = std::find(chunks.begin(), chunks.end(), chunk) != chunks.end(); 0216 if (!in_chunks) 0217 chunks.push_back(chunk); 0218 } 0219 0220 struct ChunkRange { 0221 Uint32 start; 0222 Uint32 len; 0223 }; 0224 0225 bool ChunkSelector::selectRange(Uint32 &from, Uint32 &to, Uint32 max_len) 0226 { 0227 Uint32 max_range_len = max_len; 0228 0229 const BitSet &bs = cman->getBitSet(); 0230 Uint32 num_chunks = cman->getNumChunks(); 0231 ChunkRange curr = {0, 0}; 0232 ChunkRange best = {0, 0}; 0233 0234 for (Uint32 i = 0; i < num_chunks; i++) { 0235 if (!bs.get(i) && downer->canDownloadFromWebSeed(i)) { 0236 if (curr.start == 0 && curr.len == 0) { // we have a new current range 0237 curr.start = i; 0238 curr.len = 1; 0239 } else 0240 curr.len++; // expand the current range 0241 0242 if (curr.len >= max_range_len) { 0243 // current range is bigger then the maximum range length, select it 0244 from = curr.start; 0245 to = curr.start + curr.len - 1; 0246 return true; 0247 } else if (i == num_chunks - 1) { 0248 if (curr.start > 0 && curr.len > best.len) 0249 best = curr; 0250 } 0251 } else { 0252 // see if the current range has ended 0253 // and if it is better then the best range 0254 if (curr.start > 0 && curr.len > best.len) 0255 best = curr; // we have a new best range 0256 0257 curr.start = curr.len = 0; // reset the current range 0258 } 0259 } 0260 0261 // end of the list, so select the best 0262 if (best.len) { 0263 from = best.start; 0264 to = best.start + best.len - 1; 0265 return true; 0266 } 0267 return false; 0268 } 0269 }