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 }