From c798ab30e14133cd2edf80d98ee215b7a05ac60f Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Thu, 31 Jan 2008 17:52:29 +0000 Subject: [PATCH] rewrote the piece picker to be more cpu and memory efficient. replaces the concept of sequential-download-threshold with just a sequential download settings --- ChangeLog | 2 + docs/manual.rst | 22 +- examples/client_test.cpp | 19 +- include/libtorrent/piece_picker.hpp | 105 ++- include/libtorrent/torrent.hpp | 24 +- include/libtorrent/torrent_handle.hpp | 2 +- src/peer_connection.cpp | 86 +- src/piece_picker.cpp | 1120 +++++++++++++------------ src/torrent.cpp | 54 +- src/torrent_handle.cpp | 4 +- test/test_piece_picker.cpp | 40 +- 11 files changed, 822 insertions(+), 656 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9a36cb84e..9a477e6ba 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,5 @@ + * New, more memory efficient, piece picker with sequential download + support (instead of the more complicated sequential download threshold). * Auto Upload slots. Automtically opens up more slots if upload limit is not met. * Improved NAT-PMP support by querying the default gateway (no diff --git a/docs/manual.rst b/docs/manual.rst index d1980e79a..f2e69f9e7 100644 --- a/docs/manual.rst +++ b/docs/manual.rst @@ -1427,7 +1427,7 @@ Its declaration looks like this:: int upload_limit() const; void set_download_limit(int limit) const; int download_limit() const; - void set_sequenced_download_threshold(int threshold) const; + void set_sequential_download(bool sd) const; void set_peer_upload_limit(asio::ip::tcp::endpoint ip, int limit) const; void set_peer_download_limit(asio::ip::tcp::endpoint ip, int limit) const; @@ -1658,24 +1658,18 @@ limit. download, respectively. -set_sequenced_download_threshold() ----------------------------------- +set_sequential_download() +------------------------- :: - void set_sequenced_download_threshold(int threshold); + void set_sequential_download(bool sd); -sequenced-download threshold is the limit on how popular a piece has to be -(popular == inverse of rarity) to be downloaded in sequence instead of in -random (rarest first) order. It can be used to tweak disk performance in -settings where the random download property is less necessary. For example, if -the threshold is 10, all pieces which 10 or more peers have, will be downloaded -in index order. This setting defaults to 100, which means that it is disabled -in practice. +Enables or disables *sequential download*. When enabled, the piece picker will pick pieces in sequence +instead of rarest first. -Setting this threshold to a very small value will affect the piece distribution -negatively in the swarm. It should basically only be used in situations where -the random seeks on the disk is the download bottleneck. +Enabling sequential download will affect the piece distribution negatively in the swarm. It should be +used sparingly. set_peer_upload_limit() set_peer_download_limit() diff --git a/examples/client_test.cpp b/examples/client_test.cpp index e5f8cd031..a70e17df9 100644 --- a/examples/client_test.cpp +++ b/examples/client_test.cpp @@ -476,7 +476,6 @@ void add_torrent(libtorrent::session& ses h.set_max_connections(50); h.set_max_uploads(-1); h.set_ratio(preferred_ratio); - h.set_sequenced_download_threshold(15); h.set_upload_limit(torrent_upload_limit); h.set_download_limit(torrent_download_limit); #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES @@ -860,7 +859,6 @@ int main(int ac, char* av[]) h.set_max_connections(50); h.set_max_uploads(-1); h.set_ratio(preferred_ratio); - h.set_sequenced_download_threshold(15); h.set_upload_limit(torrent_upload_limit); h.set_download_limit(torrent_download_limit); continue; @@ -878,7 +876,6 @@ int main(int ac, char* av[]) h.set_max_connections(50); h.set_max_uploads(-1); h.set_ratio(preferred_ratio); - h.set_sequenced_download_threshold(15); h.set_upload_limit(torrent_upload_limit); h.set_download_limit(torrent_download_limit); continue; @@ -903,6 +900,7 @@ int main(int ac, char* av[]) bool print_downloads = false; bool print_piece_bar = false; bool print_file_progress = false; + bool sequential_download = false; for (;;) { @@ -930,7 +928,7 @@ int main(int ac, char* av[]) break; } - if(c == 'r') + if (c == 'r') { // force reannounce on all torrents std::for_each(handles.begin(), handles.end() @@ -938,7 +936,16 @@ int main(int ac, char* av[]) , bind(&handles_t::value_type::second, _1))); } - if(c == 'p') + if (c == 's') + { + // toggle torrents between sequential and rarest first mode + sequential_download = !sequential_download; + std::for_each(handles.begin(), handles.end() + , bind(&torrent_handle::set_sequential_download + , bind(&handles_t::value_type::second, _1), sequential_download)); + } + + if (c == 'p') { // pause all torrents std::for_each(handles.begin(), handles.end() @@ -946,7 +953,7 @@ int main(int ac, char* av[]) , bind(&handles_t::value_type::second, _1))); } - if(c == 'u') + if (c == 'u') { // unpause all torrents std::for_each(handles.begin(), handles.end() diff --git a/include/libtorrent/piece_picker.hpp b/include/libtorrent/piece_picker.hpp index ef69c3334..336cb66b0 100755 --- a/include/libtorrent/piece_picker.hpp +++ b/include/libtorrent/piece_picker.hpp @@ -133,7 +133,8 @@ namespace libtorrent void get_availability(std::vector& avail) const; - void set_sequenced_download_threshold(int sequenced_download_threshold); + void sequential_download(bool sd); + bool sequential_download() const { return m_sequential_download >= 0; } // the vector tells which pieces we already have // and which we don't have. @@ -143,12 +144,16 @@ namespace libtorrent , std::vector& verify_pieces); // increases the peer count for the given piece - // (is used when a HAVE or BITFIELD message is received) + // (is used when a HAVE message is received) void inc_refcount(int index); + void dec_refcount(int index); + // increases the peer count for the given piece + // (is used when a BITFIELD message is received) + void inc_refcount(std::vector const& bitmask); // decreases the peer count for the given piece // (used when a peer disconnects) - void dec_refcount(int index); + void dec_refcount(std::vector const& bitmask); // these will increase and decrease the peer count // of all pieces. They are used when seeds join @@ -274,9 +279,11 @@ namespace libtorrent #ifndef NDEBUG // used in debug mode + void verify_priority(int start, int end, int prio) const; void check_invariant(const torrent* t = 0) const; void verify_pick(std::vector const& picked , std::vector const& bitfield) const; + void print_pieces() const; #endif // functor that compares indices on downloading_pieces @@ -295,6 +302,8 @@ namespace libtorrent private: + friend struct piece_pos; + bool can_pick(int piece, std::vector const& bitmask) const; std::pair expand_piece(int piece, int whole_pieces , std::vector const& have) const; @@ -346,26 +355,20 @@ namespace libtorrent bool filtered() const { return piece_priority == filter_priority; } void filtered(bool f) { piece_priority = f ? filter_priority : 0; } - int priority(int limit) const + int priority(piece_picker const* picker) const { - if (downloading || filtered() || have()) return 0; + if (downloading || filtered() + || have() || peer_count + picker->m_seeds == 0) + return -1; + + // priority 5, 6 and 7 disregards availability of the piece + if (piece_priority > 4) return 7 - piece_priority; + // pieces we are currently downloading have high priority - int prio = peer_count * 2; - // if the peer_count is 0 or 1, the priority cannot be higher - if (prio <= 1) return prio; - if (prio >= limit * 2) prio = limit * 2; - // the different priority levels - switch (piece_priority) - { - case 1: return prio; - case 2: return prio - 1; - case 3: return (std::max)(prio / 2, 1); - case 4: return (std::max)(prio / 2 - 1, 1); - case 5: return (std::max)(prio / 3, 1); - case 6: return (std::max)(prio / 3 - 1, 1); - case 7: return 1; - } - return prio; + int prio = peer_count * 4; +// if (prio >= picker->m_prio_limit * 6) prio = picker->m_prio_limit * 6; + + return prio + (4 - piece_priority); } bool operator!=(piece_pos p) const @@ -378,27 +381,44 @@ namespace libtorrent BOOST_STATIC_ASSERT(sizeof(piece_pos) == sizeof(char) * 4); - bool is_ordered(int priority) const - { - return priority >= m_sequenced_download_threshold * 2; - } + void update_pieces() const; + // fills in the range [start, end) of pieces in + // m_pieces that have priority 'prio' + void priority_range(int prio, int* start, int* end); + + // adds the piece 'index' to m_pieces void add(int index); - void move(int vec_index, int elem_index); + // removes the piece with the given priority and the + // elem_index in the m_pieces vector + void remove(int priority, int elem_index); + // updates the position of the piece with the given + // priority and the elem_index in the m_pieces vector + void update(int priority, int elem_index); + // shuffles the given piece inside it's priority range + void shuffle(int priority, int elem_index); + void sort_piece(std::vector::iterator dp); downloading_piece& add_download_piece(); void erase_download_piece(std::vector::iterator i); - // this vector contains all pieces we don't have. - // in the first entry (index 0) is a vector of all pieces - // that no peer have, the vector at index 1 contains - // all pieces that exactly one peer have, index 2 contains - // all pieces exactly two peers have and so on. - // this is not entirely true. The availibility of a piece - // is adjusted depending on its priority. But the principle - // is that the higher index, the lower priority a piece has. - std::vector > m_piece_info; + // the number of seeds. These are not added to + // the availability counters of the pieces + int m_seeds; + + // the following vectors are mutable because they sometimes may + // be updated lazily, triggered by const functions + + // this vector contains all piece indices that are pickable + // sorted by priority. Pieces are in random random order + // among pieces with the same priority + mutable std::vector m_pieces; + + // these are indices to the priority boundries inside + // the m_pieces vector. priority 0 always start at + // 0, priority 1 starts at m_priority_boundries[0] etc. + mutable std::vector m_priority_boundries; // this maps indices to number of peers that has this piece and // index into the m_piece_info vectors. @@ -406,7 +426,7 @@ namespace libtorrent // doesn't exist in the piece_info buckets // pieces with the filtered flag set doesn't have entries in // the m_piece_info buckets either - std::vector m_piece_map; + mutable std::vector m_piece_map; // each piece that's currently being downloaded // has an entry in this list with block allocations. @@ -438,9 +458,16 @@ namespace libtorrent // the number of pieces we have int m_num_have; - // the required popularity of a piece in order to download - // it in sequence instead of random order. - int m_sequenced_download_threshold; + // -1 means sequential download is not active. + // >= 0 means that pieces are requested in sequential order + // and this variable is the next piece to request. + // in that case m_pieces is cleared and not used. + int m_sequential_download; + + // if this is set to true, it means update_pieces() + // has to be called before accessing m_pieces. + mutable bool m_dirty; + #ifndef NDEBUG bool m_files_checked_called; #endif diff --git a/include/libtorrent/torrent.hpp b/include/libtorrent/torrent.hpp index 31b77aa55..35f6e18d5 100755 --- a/include/libtorrent/torrent.hpp +++ b/include/libtorrent/torrent.hpp @@ -157,7 +157,7 @@ namespace libtorrent aux::session_impl& session() { return m_ses; } - void set_sequenced_download_threshold(int threshold); + void set_sequential_download(bool sd); bool verify_resume_data(entry& rd, std::string& error) { TORRENT_ASSERT(m_storage); return m_storage->verify_resume_data(rd, error); } @@ -371,8 +371,7 @@ namespace libtorrent int num_pieces() const { return m_num_pieces; } - // when we get a have- or bitfield- messages, this is called for every - // piece a peer has gained. + // when we get a have message, this is called for that piece void peer_has(int index) { if (m_picker.get()) @@ -389,6 +388,22 @@ namespace libtorrent #endif } + // when we get a bitfield message, this is called for that piece + void peer_has(std::vector const& bitfield) + { + if (m_picker.get()) + { + TORRENT_ASSERT(!is_seed()); + m_picker->inc_refcount(bitfield); + } +#ifndef NDEBUG + else + { + TORRENT_ASSERT(is_seed()); + } +#endif + } + void peer_has_all() { if (m_picker.get()) @@ -404,7 +419,6 @@ namespace libtorrent #endif } - // when peer disconnects, this is called for every piece it had void peer_lost(int index) { if (m_picker.get()) @@ -725,7 +739,7 @@ namespace libtorrent // in case the piece picker hasn't been constructed // when this settings is set, this variable will keep // its value until the piece picker is created - int m_sequenced_download_threshold; + bool m_sequential_download; // is false by default and set to // true when the first tracker reponse diff --git a/include/libtorrent/torrent_handle.hpp b/include/libtorrent/torrent_handle.hpp index 14217d9a4..759944f56 100755 --- a/include/libtorrent/torrent_handle.hpp +++ b/include/libtorrent/torrent_handle.hpp @@ -378,7 +378,7 @@ namespace libtorrent void set_download_limit(int limit) const; int download_limit() const; - void set_sequenced_download_threshold(int threshold) const; + void set_sequential_download(bool sd) const; void set_peer_upload_limit(tcp::endpoint ip, int limit) const; void set_peer_download_limit(tcp::endpoint ip, int limit) const; diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp index 03915cdeb..7223ed116 100755 --- a/src/peer_connection.cpp +++ b/src/peer_connection.cpp @@ -349,10 +349,12 @@ namespace libtorrent m_have_piece.resize(t->torrent_file().num_pieces(), m_have_all); // now that we have a piece_picker, - // update it with this peers pieces + // update it with this peer's pieces - int num_pieces = std::count(m_have_piece.begin(), m_have_piece.end(), true); - if (num_pieces == int(m_have_piece.size())) + TORRENT_ASSERT(m_num_pieces == std::count(m_have_piece.begin() + , m_have_piece.end(), true)); + + if (m_num_pieces == int(m_have_piece.size())) { #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << " *** THIS IS A SEED ***\n"; @@ -365,23 +367,21 @@ namespace libtorrent disconnect("seed to seed connection redundant"); return; } - m_num_pieces = num_pieces; t->peer_has_all(); if (!t->is_finished()) t->get_policy().peer_is_interesting(*this); return; } - m_num_pieces = num_pieces; // if we're a seed, we don't keep track of piece availability if (!t->is_seed()) { + t->peer_has(m_have_piece); bool interesting = false; for (int i = 0; i < int(m_have_piece.size()); ++i) { if (m_have_piece[i]) { - t->peer_has(i); // if the peer has a piece and we don't, the peer is interesting if (!t->have_piece(i) && t->picker().piece_priority(i) != 0) @@ -627,7 +627,19 @@ namespace libtorrent TORRENT_ASSERT(m_torrent.expired()); // check to make sure we don't have another connection with the same // info_hash and peer_id. If we do. close this connection. +#ifndef NDEBUG + try + { +#endif t->attach_peer(this); +#ifndef NDEBUG + } + catch (std::exception& e) + { + std::cout << e.what() << std::endl; + TORRENT_ASSERT(false); + } +#endif if (m_disconnecting) return; m_torrent = wpt; @@ -1091,49 +1103,31 @@ namespace libtorrent // let the torrent know which pieces the // peer has // if we're a seed, we don't keep track of piece availability + bool interesting = false; if (!t->is_seed()) { - bool interesting = false; + t->peer_has(bitfield); + for (int i = 0; i < (int)m_have_piece.size(); ++i) { bool have = bitfield[i]; if (have && !m_have_piece[i]) { - m_have_piece[i] = true; - ++m_num_pieces; - t->peer_has(i); if (!t->have_piece(i) && t->picker().piece_priority(i) != 0) interesting = true; } else if (!have && m_have_piece[i]) { // this should probably not be allowed - m_have_piece[i] = false; - --m_num_pieces; t->peer_lost(i); } } + } - if (interesting) t->get_policy().peer_is_interesting(*this); - } - else - { - for (int i = 0; i < (int)m_have_piece.size(); ++i) - { - bool have = bitfield[i]; - if (have && !m_have_piece[i]) - { - m_have_piece[i] = true; - ++m_num_pieces; - } - else if (!have && m_have_piece[i]) - { - // this should probably not be allowed - m_have_piece[i] = false; - --m_num_pieces; - } - } - } + m_have_piece = bitfield; + m_num_pieces = num_pieces; + + if (interesting) t->get_policy().peer_is_interesting(*this); } // ----------------------------- @@ -1337,8 +1331,10 @@ namespace libtorrent #ifndef NDEBUG check_postcondition post_checker_(t); +#if !defined TORRENT_DISABLE_INVARIANT_CHECKS t->check_invariant(); #endif +#endif #ifdef TORRENT_VERBOSE_LOGGING (*m_logger) << time_now_string() @@ -1450,7 +1446,7 @@ namespace libtorrent m_outstanding_writing_bytes += p.length; TORRENT_ASSERT(!m_channel_state[download_channel] != peer_info::bw_network); picker.mark_as_writing(block_finished, peer_info_struct()); -#ifndef NDEBUG +#if !defined NDEBUG && !defined TORRENT_DISABLE_INVARIANT_CHECKS t->check_invariant(); #endif } @@ -2097,6 +2093,14 @@ namespace libtorrent m_torrent.reset(); } +#ifndef NDEBUG + // since this connection doesn't have a torrent reference + // no torrent should have a reference to this connection either + for (aux::session_impl::torrent_map::const_iterator i = m_ses.m_torrents.begin() + , end(m_ses.m_torrents.end()); i != end; ++i) + TORRENT_ASSERT(!i->second->has_peer(this)); +#endif + boost::shared_ptr sock = m_socket; m_disconnecting = true; m_ses.close_connection(this, message); @@ -3013,12 +3017,13 @@ namespace libtorrent #ifndef NDEBUG void peer_connection::check_invariant() const { + boost::shared_ptr t = m_torrent.lock(); if (m_disconnecting) { for (aux::session_impl::torrent_map::const_iterator i = m_ses.m_torrents.begin() , end(m_ses.m_torrents.end()); i != end; ++i) TORRENT_ASSERT(!i->second->has_peer((peer_connection*)this)); - TORRENT_ASSERT(!m_torrent.lock()); + TORRENT_ASSERT(!t); } else if (!m_in_constructor) { @@ -3053,8 +3058,15 @@ namespace libtorrent TORRENT_ASSERT(!is_choked()); } - boost::shared_ptr t = m_torrent.lock(); - if (!t) return; + if (!t) + { + // since this connection doesn't have a torrent reference + // no torrent should have a reference to this connection either + for (aux::session_impl::torrent_map::const_iterator i = m_ses.m_torrents.begin() + , end(m_ses.m_torrents.end()); i != end; ++i) + TORRENT_ASSERT(!i->second->has_peer((peer_connection*)this)); + return; + } if (m_peer_info) { @@ -3208,7 +3220,7 @@ namespace libtorrent time_duration time_limit = seconds( m_ses.settings().inactivity_timeout); - // don't bother disconnect peers we haven't been intersted + // don't bother disconnect peers we haven't been interested // in (and that hasn't been interested in us) for a while // unless we have used up all our connection slots if (!m_interesting diff --git a/src/piece_picker.cpp b/src/piece_picker.cpp index dbdba17f0..0a60c94d4 100755 --- a/src/piece_picker.cpp +++ b/src/piece_picker.cpp @@ -48,20 +48,27 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/torrent.hpp" #endif -//#define TORRENT_PIECE_PICKER_INVARIANT_CHECK INVARIANT_CHECK -#define TORRENT_PIECE_PICKER_INVARIANT_CHECK +#define TORRENT_PIECE_PICKER_INVARIANT_CHECK INVARIANT_CHECK +//#define TORRENT_PIECE_PICKER_INVARIANT_CHECK + +//#define TORRENT_PICKER_LOG namespace libtorrent { piece_picker::piece_picker(int blocks_per_piece, int total_num_blocks) - : m_piece_info(2) + : m_seeds(0) + , m_priority_boundries(1, int(m_pieces.size())) , m_piece_map((total_num_blocks + blocks_per_piece-1) / blocks_per_piece) , m_num_filtered(0) , m_num_have_filtered(0) , m_num_have(0) - , m_sequenced_download_threshold(100) + , m_sequential_download(-1) + , m_dirty(false) { +#ifdef TORRENT_PICKER_LOG + std::cout << "new piece_picker" << std::endl; +#endif TORRENT_ASSERT(blocks_per_piece > 0); TORRENT_ASSERT(total_num_blocks >= 0); #ifndef NDEBUG @@ -84,6 +91,33 @@ namespace libtorrent std::fill(m_piece_map.begin(), m_piece_map.end() , piece_pos(0, 0)); m_num_have = 0; +#ifndef NDEBUG + check_invariant(); +#endif + } + + void piece_picker::sequential_download(bool sd) + { + if (sd == sequential_download()) return; + + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + + if (sd) + { + std::vector().swap(m_pieces); + std::vector().swap(m_priority_boundries); + + // initialize m_sdquential_download + m_sequential_download = 0; + for (std::vector::const_iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end && (i->have() || i->filtered()); + ++i, ++m_sequential_download); + } + else + { + m_sequential_download = -1; + m_dirty = true; + } } // pieces is a bitmask with the pieces we have @@ -103,6 +137,7 @@ namespace libtorrent piece_pos& p = m_piece_map[index]; if (*i) { + if (m_sequential_download == index) ++m_sequential_download; ++m_num_have; p.set_have(); if (p.filtered()) @@ -167,68 +202,6 @@ namespace libtorrent st.finished = 0; } - void piece_picker::set_sequenced_download_threshold( - int sequenced_download_threshold) - { - TORRENT_PIECE_PICKER_INVARIANT_CHECK; - - if (sequenced_download_threshold == m_sequenced_download_threshold) - return; - - TORRENT_ASSERT(sequenced_download_threshold > 0); - if (sequenced_download_threshold <= 0) return; - - int old_limit = m_sequenced_download_threshold; - m_sequenced_download_threshold = sequenced_download_threshold; - - for (std::vector::iterator i = m_piece_map.begin() - , end(m_piece_map.end()); i != end; ++i) - { - if (i->priority(old_limit) != i->priority(m_sequenced_download_threshold)) - { - piece_pos& p = *i; - int prev_priority = p.priority(old_limit); - if (prev_priority == 0) continue; - move(prev_priority, p.index); - } - } - - typedef std::vector info_t; - - if (old_limit < sequenced_download_threshold) - { - // the threshold was incremented, in case - // the previous max availability was reached - // we need to shuffle that bucket, if not, we - // don't have to do anything - if (int(m_piece_info.size()) > old_limit * 2) - { - info_t& in = m_piece_info[old_limit * 2]; - std::random_shuffle(in.begin(), in.end()); - int c = 0; - for (info_t::iterator i = in.begin() - , end(in.end()); i != end; ++i) - { - m_piece_map[*i].index = c++; - TORRENT_ASSERT(m_piece_map[*i].priority(old_limit) == old_limit * 2); - } - } - } - else if (int(m_piece_info.size()) > sequenced_download_threshold * 2) - { - info_t& in = m_piece_info[sequenced_download_threshold * 2]; - std::sort(in.begin(), in.end()); - int c = 0; - for (info_t::iterator i = in.begin() - , end(in.end()); i != end; ++i) - { - m_piece_map[*i].index = c++; - TORRENT_ASSERT(m_piece_map[*i].priority( - sequenced_download_threshold) == sequenced_download_threshold * 2); - } - } - } - piece_picker::downloading_piece& piece_picker::add_download_piece() { int num_downloads = m_downloads.size(); @@ -289,15 +262,75 @@ namespace libtorrent } } + void piece_picker::verify_priority(int range_start, int range_end, int prio) const + { + TORRENT_ASSERT(range_start <= range_end); + TORRENT_ASSERT(range_end <= int(m_pieces.size())); + for (std::vector::const_iterator i = m_pieces.begin() + range_start + , end(m_pieces.begin() + range_end); i != end; ++i) + { + int index = *i; + TORRENT_ASSERT(index >= 0); + TORRENT_ASSERT(index < int(m_piece_map.size())); + int p = m_piece_map[index].priority(this); + TORRENT_ASSERT(p == prio); + } + } +#ifndef NDEBUG + void piece_picker::print_pieces() const + { + for (std::vector::const_iterator i = m_priority_boundries.begin() + , end(m_priority_boundries.end()); i != end; ++i) + { + std::cout << *i << " "; + } + std::cout << std::endl; + int index = 0; + std::vector::const_iterator j = m_priority_boundries.begin(); + for (std::vector::const_iterator i = m_pieces.begin() + , end(m_pieces.end()); i != end; ++i, ++index) + { + if (*i == -1) break; + while (j != m_priority_boundries.end() && *j <= index) + { + std::cout << "| "; + ++j; + } + std::cout << *i << "(" << m_piece_map[*i].index << ") "; + } + std::cout << std::endl; + } +#endif void piece_picker::check_invariant(const torrent* t) const { + if (m_sequential_download == -1 && !m_dirty) + { + TORRENT_ASSERT(!m_priority_boundries.empty()); + int prio = 0; + int start = 0; + for (std::vector::const_iterator i = m_priority_boundries.begin() + , end(m_priority_boundries.end()); i != end; ++i) + { + verify_priority(start, *i, prio); + ++prio; + start = *i; + } + TORRENT_ASSERT(m_priority_boundries.back() == int(m_pieces.size())); + } + else if (m_sequential_download >= 0) + { + int index = 0; + for (std::vector::const_iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end && (i->have() || i->filtered()); + ++i, ++index); + TORRENT_ASSERT(m_sequential_download == index); + } + TORRENT_ASSERT(sizeof(piece_pos) == 4); TORRENT_ASSERT(m_num_have >= 0); TORRENT_ASSERT(m_num_have_filtered >= 0); TORRENT_ASSERT(m_num_filtered >= 0); - TORRENT_ASSERT(m_piece_info.empty() || m_piece_info[0].empty()); - if (!m_downloads.empty()) { for (std::vector::const_iterator i = m_downloads.begin(); @@ -312,9 +345,6 @@ namespace libtorrent if (t != 0) TORRENT_ASSERT((int)m_piece_map.size() == t->torrent_file().num_pieces()); - for (int i = m_sequenced_download_threshold * 2 + 1; i < int(m_piece_info.size()); ++i) - TORRENT_ASSERT(m_piece_info[i].empty()); - for (std::vector::const_iterator i = m_downloads.begin() , end(m_downloads.end()); i != end; ++i) { @@ -354,14 +384,16 @@ namespace libtorrent i != m_piece_map.end(); ++i) { int index = static_cast(i - m_piece_map.begin()); - if (i->filtered()) + piece_pos const& p = *i; + + if (p.filtered()) { - if (i->index != piece_pos::we_have_index) + if (p.index != piece_pos::we_have_index) ++num_filtered; else ++num_have_filtered; } - if (i->index == piece_pos::we_have_index) + if (p.index == piece_pos::we_have_index) ++num_have; #if 0 @@ -399,46 +431,39 @@ namespace libtorrent } #endif - if (i->index == piece_pos::we_have_index) + if (p.index == piece_pos::we_have_index) { TORRENT_ASSERT(t == 0 || t->have_piece(index)); - TORRENT_ASSERT(i->downloading == 0); -/* - // make sure there's no entry - // with this index. (there shouldn't - // be since the piece_map is piece_pos::we_have_index) - for (int i = 0; i < int(m_piece_info.size()); ++i) - { - for (int j = 0; j < int(m_piece_info[i].size()); ++j) - { - TORRENT_ASSERT(m_piece_info[i][j] != index); - } - } -*/ + TORRENT_ASSERT(p.downloading == 0); } - else - { - if (t != 0) - TORRENT_ASSERT(!t->have_piece(index)); - int prio = i->priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prio < int(m_piece_info.size())); - if (prio > 0) + if (t != 0) + TORRENT_ASSERT(!t->have_piece(index)); + + if (m_sequential_download == -1 && !m_dirty) + { + int prio = p.priority(this); + TORRENT_ASSERT(prio < int(m_priority_boundries.size()) + || m_dirty); + if (prio >= 0) { - const std::vector& vec = m_piece_info[prio]; - assert (i->index < vec.size()); - TORRENT_ASSERT(vec[i->index] == index); + TORRENT_ASSERT(p.index < m_pieces.size()); + TORRENT_ASSERT(m_pieces[p.index] == index); } -/* - for (int k = 0; k < int(m_piece_info.size()); ++k) + else { - for (int j = 0; j < int(m_piece_info[k].size()); ++j) - { - TORRENT_ASSERT(int(m_piece_info[k][j]) != index - || (prio > 0 && prio == k && int(i->index) == j)); - } + TORRENT_ASSERT(prio == -1); + // make sure there's no entry + // with this index. (there shouldn't + // be since the priority is -1) + TORRENT_ASSERT(std::find(m_pieces.begin(), m_pieces.end(), index) + == m_pieces.end()); } -*/ + } + else if (m_sequential_download >= 0) + { + TORRENT_ASSERT(m_pieces.empty()); + TORRENT_ASSERT(m_priority_boundries.empty()); } int count = std::count_if(m_downloads.begin(), m_downloads.end() @@ -455,6 +480,15 @@ namespace libtorrent TORRENT_ASSERT(num_have == m_num_have); TORRENT_ASSERT(num_filtered == m_num_filtered); TORRENT_ASSERT(num_have_filtered == m_num_have_filtered); + + if (!m_dirty) + { + for (std::vector::const_iterator i = m_pieces.begin() + , end(m_pieces.end()); i != end; ++i) + { + TORRENT_ASSERT(m_piece_map[*i].priority(this) >= 0); + } + } } #endif @@ -491,174 +525,258 @@ namespace libtorrent } } TORRENT_ASSERT(integer_part + fraction_part == num_pieces); - return float(min_availability) + (fraction_part / num_pieces); + return float(min_availability + m_seeds) + (fraction_part / num_pieces); + } + + void piece_picker::priority_range(int prio, int* start, int* end) + { + TORRENT_ASSERT(prio >= 0); + TORRENT_ASSERT(prio < int(m_priority_boundries.size()) + || m_dirty); + if (prio == 0) *start = 0; + else *start = m_priority_boundries[prio - 1]; + *end = m_priority_boundries[prio]; + TORRENT_ASSERT(*start <= *end); } void piece_picker::add(int index) { + TORRENT_ASSERT(!m_dirty); TORRENT_ASSERT(index >= 0); TORRENT_ASSERT(index < int(m_piece_map.size())); piece_pos& p = m_piece_map[index]; TORRENT_ASSERT(!p.filtered()); TORRENT_ASSERT(!p.have()); + TORRENT_ASSERT(m_sequential_download == -1); - int priority = p.priority(m_sequenced_download_threshold); - TORRENT_ASSERT(priority > 0); - if (int(m_piece_info.size()) <= priority) - m_piece_info.resize(priority + 1); + int priority = p.priority(this); + TORRENT_ASSERT(priority >= 0); + if (int(m_priority_boundries.size()) <= priority) + m_priority_boundries.resize(priority + 1, m_pieces.size()); - TORRENT_ASSERT(int(m_piece_info.size()) > priority); + TORRENT_ASSERT(int(m_priority_boundries.size()) >= priority); - if (is_ordered(priority)) + int range_start, range_end; + priority_range(priority, &range_start, &range_end); + int new_index; + if (range_end == range_start) new_index = range_start; + else new_index = rand() % (range_end - range_start) + range_start; + +#ifdef TORRENT_PICKER_LOG + std::cout << "add " << index << " (" << priority << ")" << std::endl; + print_pieces(); +#endif + m_pieces.push_back(-1); + + for (;;) { - // the piece should be inserted ordered, not randomly - std::vector& v = m_piece_info[priority]; -// TORRENT_ASSERT(is_sorted(v.begin(), v.end()/*, std::greater()*/)); - std::vector::iterator i = std::lower_bound(v.begin(), v.end() - , index/*, std::greater()*/); - p.index = i - v.begin(); - v.insert(i, index); - i = v.begin() + p.index + 1; - for (;i != v.end(); ++i) + TORRENT_ASSERT(new_index < int(m_pieces.size())); + int temp = m_pieces[new_index]; + m_pieces[new_index] = index; + m_piece_map[index].index = new_index; + index = temp; + do { - ++m_piece_map[*i].index; - TORRENT_ASSERT(v[m_piece_map[*i].index] == *i); - } -// TORRENT_ASSERT(is_sorted(v.begin(), v.end()/*, std::greater()*/)); + temp = m_priority_boundries[priority]++; + ++priority; + } while (temp == new_index && priority < int(m_priority_boundries.size())); + new_index = temp; +#ifdef TORRENT_PICKER_LOG + print_pieces(); + std::cout << " index: " << index + << " prio: " << priority + << " new_index: " << new_index + << std::endl; +#endif + if (priority >= int(m_priority_boundries.size())) break; + TORRENT_ASSERT(temp >= 0); } - else if (m_piece_info[priority].size() < 2) + if (index != -1) { - p.index = m_piece_info[priority].size(); - m_piece_info[priority].push_back(index); - } - else - { - // find a random position in the destination vector where we will place - // this entry. - int dst_index = rand() % m_piece_info[priority].size(); - - // copy the entry at that position to the back - m_piece_map[m_piece_info[priority][dst_index]].index - = m_piece_info[priority].size(); - m_piece_info[priority].push_back(m_piece_info[priority][dst_index]); + TORRENT_ASSERT(new_index == int(m_pieces.size() - 1)); + m_pieces[new_index] = index; + m_piece_map[index].index = new_index; - // and then replace the one at dst_index with the one we're moving. - // this procedure is to make sure there's no ordering when pieces - // are moved in sequenced order. - p.index = dst_index; - m_piece_info[priority][p.index] = index; +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif } } - // will update the piece with the given properties (priority, elem_index) - // to place it at the correct position in the vectors. - void piece_picker::move(int priority, int elem_index) + void piece_picker::remove(int priority, int elem_index) { - TORRENT_ASSERT(priority > 0); + TORRENT_ASSERT(!m_dirty); + TORRENT_ASSERT(priority >= 0); + TORRENT_ASSERT(m_sequential_download == -1); + +#ifdef TORRENT_PICKER_LOG + std::cout << "remove " << m_pieces[elem_index] << " (" << priority << ")" << std::endl; +#endif + int next_index = elem_index; + TORRENT_ASSERT(m_piece_map[m_pieces[elem_index]].priority(this) == -1); + for (;;) + { +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + TORRENT_ASSERT(elem_index < int(m_pieces.size())); + int temp; + do + { + temp = --m_priority_boundries[priority]; + ++priority; + } while (next_index == temp && priority < int(m_priority_boundries.size())); + if (next_index == temp) break; + next_index = temp; + + int piece = m_pieces[next_index]; + m_pieces[elem_index] = piece; + m_piece_map[piece].index = elem_index; + TORRENT_ASSERT(m_piece_map[piece].priority(this) == priority - 1); + TORRENT_ASSERT(elem_index < int(m_pieces.size() - 1)); + elem_index = next_index; + + if (priority == int(m_priority_boundries.size())) + break; + } + m_pieces.pop_back(); + TORRENT_ASSERT(next_index == int(m_pieces.size())); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + } + + // will update the piece with the given properties (priority, elem_index) + // to place it at the correct position + void piece_picker::update(int priority, int elem_index) + { + TORRENT_ASSERT(!m_dirty); + TORRENT_ASSERT(priority >= 0); TORRENT_ASSERT(elem_index >= 0); TORRENT_ASSERT(m_files_checked_called); + TORRENT_ASSERT(m_sequential_download == -1); - TORRENT_ASSERT(int(m_piece_info.size()) > priority); - TORRENT_ASSERT(int(m_piece_info[priority].size()) > elem_index); + TORRENT_ASSERT(int(m_priority_boundries.size()) > priority); - int index = m_piece_info[priority][elem_index]; + int index = m_pieces[elem_index]; // update the piece_map piece_pos& p = m_piece_map[index]; TORRENT_ASSERT(int(p.index) == elem_index || p.have()); - int new_priority = p.priority(m_sequenced_download_threshold); + int new_priority = p.priority(this); if (new_priority == priority) return; - if (int(m_piece_info.size()) <= new_priority - && new_priority > 0) + if (new_priority == -1) { - m_piece_info.resize(new_priority + 1); - TORRENT_ASSERT(int(m_piece_info.size()) > new_priority); + remove(priority, elem_index); + return; } - if (new_priority == 0) + if (int(m_priority_boundries.size()) <= new_priority) + m_priority_boundries.resize(new_priority + 1, m_pieces.size()); + +#ifdef TORRENT_PICKER_LOG + std::cout << "update " << index << " (" << priority << "->" << new_priority << ")" << std::endl; +#endif + if (priority > new_priority) { - // this means the piece should not have an entry - } - else if (is_ordered(new_priority)) - { - // the piece should be inserted ordered, not randomly - std::vector& v = m_piece_info[new_priority]; -// TORRENT_ASSERT(is_sorted(v.begin(), v.end()/*, std::greater()*/)); - std::vector::iterator i = std::lower_bound(v.begin(), v.end() - , index/*, std::greater()*/); - p.index = i - v.begin(); - v.insert(i, index); - i = v.begin() + p.index + 1; - for (;i != v.end(); ++i) + int new_index; + int temp = index; + for (;;) { - ++m_piece_map[*i].index; - TORRENT_ASSERT(v[m_piece_map[*i].index] == *i); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + --priority; + new_index = m_priority_boundries[priority]++; + TORRENT_ASSERT(new_index < int(m_pieces.size())); + if (temp != m_pieces[new_index]) + { + temp = m_pieces[new_index]; + m_pieces[elem_index] = temp; + m_piece_map[temp].index = elem_index; + TORRENT_ASSERT(elem_index < int(m_pieces.size())); + } + elem_index = new_index; + if (priority == new_priority) break; } -// TORRENT_ASSERT(is_sorted(v.begin(), v.end()/*, std::greater()*/)); - } - else if (m_piece_info[new_priority].size() < 2) - { - p.index = m_piece_info[new_priority].size(); - m_piece_info[new_priority].push_back(index); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + m_pieces[elem_index] = index; + m_piece_map[index].index = elem_index; + TORRENT_ASSERT(elem_index < int(m_pieces.size())); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + shuffle(priority, elem_index); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + TORRENT_ASSERT(m_piece_map[index].priority(this) == priority); } else { - // find a random position in the destination vector where we will place - // this entry. - int dst_index = rand() % m_piece_info[new_priority].size(); - - // copy the entry at that position to the back - m_piece_map[m_piece_info[new_priority][dst_index]].index - = m_piece_info[new_priority].size(); - m_piece_info[new_priority].push_back(m_piece_info[new_priority][dst_index]); - - // and then replace the one at dst_index with the one we're moving. - // this procedure is to make sure there's no ordering when pieces - // are moved in sequenced order. - p.index = dst_index; - m_piece_info[new_priority][p.index] = index; - } - TORRENT_ASSERT(new_priority == 0 || p.index < m_piece_info[p.priority(m_sequenced_download_threshold)].size()); - TORRENT_ASSERT(new_priority == 0 || m_piece_info[p.priority(m_sequenced_download_threshold)][p.index] == index); - - if (is_ordered(priority)) - { - // remove the element from the source vector and preserve the order - std::vector& v = m_piece_info[priority]; - v.erase(v.begin() + elem_index); - for (std::vector::iterator i = v.begin() + elem_index; - i != v.end(); ++i) + int new_index; + int temp = index; + for (;;) { - --m_piece_map[*i].index; - TORRENT_ASSERT(v[m_piece_map[*i].index] == *i); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + new_index = --m_priority_boundries[priority]; + TORRENT_ASSERT(new_index < int(m_pieces.size())); + if (temp != m_pieces[new_index]) + { + temp = m_pieces[new_index]; + m_pieces[elem_index] = temp; + m_piece_map[temp].index = elem_index; + TORRENT_ASSERT(elem_index < int(m_pieces.size())); + } + elem_index = new_index; + ++priority; + if (priority == new_priority) break; } +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + m_pieces[elem_index] = index; + m_piece_map[index].index = elem_index; + TORRENT_ASSERT(elem_index < int(m_pieces.size())); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + shuffle(priority, elem_index); +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + TORRENT_ASSERT(m_piece_map[index].priority(this) == priority); } - else - { - // this will remove elem from the source vector without - // preserving order, but the order is random anyway - int replace_index = m_piece_info[priority][elem_index] = m_piece_info[priority].back(); - if (index != replace_index) - { - // update the entry we moved from the back - m_piece_map[replace_index].index = elem_index; + } - TORRENT_ASSERT(int(m_piece_info[priority].size()) > elem_index); - // this may not necessarily be the case. If we've just updated the threshold and are updating - // the piece map -// TORRENT_ASSERT((int)m_piece_map[replace_index].priority(m_sequenced_download_threshold) == priority); - TORRENT_ASSERT(int(m_piece_map[replace_index].index) == elem_index); - TORRENT_ASSERT(m_piece_info[priority][elem_index] == replace_index); - } - else - { - TORRENT_ASSERT(int(m_piece_info[priority].size()) == elem_index+1); - } + void piece_picker::shuffle(int priority, int elem_index) + { + TORRENT_ASSERT(!m_dirty); + TORRENT_ASSERT(priority >= 0); + TORRENT_ASSERT(elem_index >= 0); + TORRENT_ASSERT(m_sequential_download == -1); + + int range_start, range_end; + priority_range(priority, &range_start, &range_end); + TORRENT_ASSERT(range_start < range_end); + int other_index = rand() % (range_end - range_start) + range_start; - m_piece_info[priority].pop_back(); - } + if (other_index == elem_index) return; + + // swap other_index with elem_index + piece_pos& p1 = m_piece_map[m_pieces[other_index]]; + piece_pos& p2 = m_piece_map[m_pieces[elem_index]]; + + int temp = p1.index; + p1.index = p2.index; + p2.index = temp; + std::swap(m_pieces[other_index], m_pieces[elem_index]); } void piece_picker::sort_piece(std::vector::iterator dp) @@ -695,19 +813,20 @@ namespace libtorrent erase_download_piece(i); piece_pos& p = m_piece_map[index]; - int prev_priority = p.priority(m_sequenced_download_threshold); + int prev_priority = p.priority(this); p.downloading = 0; - int new_priority = p.priority(m_sequenced_download_threshold); + int new_priority = p.priority(this); if (new_priority == prev_priority) return; - - if (prev_priority == 0) + if (m_sequential_download >= 0) return; + if (m_dirty) return; + if (prev_priority == -1) { add(index); } else { - move(prev_priority, p.index); + update(prev_priority, p.index); } } @@ -715,97 +834,14 @@ namespace libtorrent { TORRENT_PIECE_PICKER_INVARIANT_CHECK; TORRENT_ASSERT(m_files_checked_called); - - // in general priority = availability * 2 - // see piece_block::priority() - - // this will insert two empty vectors at the start of the - // piece_info vector. It is done like this as an optimization, - // to swap vectors instead of copying them - while (m_piece_info.size() < 3 - || (!m_piece_info.rbegin()->empty()) - || (!(m_piece_info.rbegin()+1)->empty())) + ++m_seeds; + if (m_sequential_download >= 0) return; + if (m_seeds == 1) { - m_piece_info.push_back(std::vector()); - } - TORRENT_ASSERT(m_piece_info.rbegin()->empty()); - TORRENT_ASSERT((m_piece_info.rbegin()+1)->empty()); - typedef std::vector > piece_info_t; - for (piece_info_t::reverse_iterator i = m_piece_info.rbegin(), j(i+1) - , k(j+1), end(m_piece_info.rend()); k != end; ++i, ++j, ++k) - { - k->swap(*i); - } - TORRENT_ASSERT(m_piece_info.begin()->empty()); - TORRENT_ASSERT((m_piece_info.begin()+1)->empty()); - - // if we have some priorities that are clamped to the - // sequenced download, move that vector back down - int last_index = m_piece_info.size() - 1; - int cap_index = m_sequenced_download_threshold * 2; - if (last_index == cap_index) - { - // this is the case when the top bucket - // was moved up into the sequenced download bucket. - m_piece_info.push_back(std::vector()); - m_piece_info[cap_index].swap(m_piece_info[cap_index+1]); - ++last_index; - } - else if (last_index > cap_index) - { - if (last_index - cap_index == 1) - { - m_piece_info.push_back(std::vector()); - ++last_index; - } - m_piece_info[cap_index+1].swap(m_piece_info[cap_index+2]); - m_piece_info[cap_index].swap(m_piece_info[cap_index+1]); - } - - // now, increase the peer count of all the pieces. - // because of different priorities, some pieces may have - // ended up in the wrong priority bucket. Adjust that. - for (std::vector::iterator i = m_piece_map.begin() - , end(m_piece_map.end()); i != end; ++i) - { - int prev_prio = i->priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prev_prio < int(m_piece_info.size())); - ++i->peer_count; - // if the assumption that the priority would - // increase by 2 when increasing the availability - // by one isn't true for this particular piece, correct it. - // that assumption is true for all pieces with priority 0 or 1 - int new_prio = i->priority(m_sequenced_download_threshold); - TORRENT_ASSERT(new_prio <= cap_index); - if (prev_prio == 0 && new_prio > 0) - { - add(i - m_piece_map.begin()); - continue; - } - if (new_prio == 0) - { - TORRENT_ASSERT(prev_prio == 0); - continue; - } - if (prev_prio == cap_index) - { - TORRENT_ASSERT(new_prio == cap_index); - continue; - } - if (new_prio == prev_prio + 2 && new_prio != cap_index) - { - TORRENT_ASSERT(new_prio != cap_index); - continue; - } - if (prev_prio + 2 >= cap_index) - { - // these two vectors will have moved one extra step - // passed the sequenced download threshold - ++prev_prio; - } - TORRENT_ASSERT(prev_prio + 2 != cap_index); - TORRENT_ASSERT(prev_prio + 2 != new_prio); - move(prev_prio + 2, i->index); + // when m_seeds is increased from 0 to 1 + // we may have to add pieces that previously + // didn't have any peers + m_dirty = true; } } @@ -813,150 +849,188 @@ namespace libtorrent { TORRENT_PIECE_PICKER_INVARIANT_CHECK; TORRENT_ASSERT(m_files_checked_called); - TORRENT_ASSERT(m_piece_info.size() >= 2); - TORRENT_ASSERT(m_piece_info.front().empty()); - // swap all vectors two steps down - if (m_piece_info.size() > 2) - { - typedef std::vector > piece_info_t; - for (piece_info_t::iterator i = m_piece_info.begin(), j(i+1) - , k(j+1), end(m_piece_info.end()); k != end; ++i, ++j, ++k) - { - k->swap(*i); - } - } - else - { - m_piece_info.resize(3); - } - int last_index = m_piece_info.size() - 1; - if ((m_piece_info.size() & 1) == 0) - { - // if there's an even number of vectors, swap - // the last two to get the same layout in both cases - m_piece_info[last_index].swap(m_piece_info[last_index-1]); - } - TORRENT_ASSERT(m_piece_info.back().empty()); - int pushed_out_index = m_piece_info.size() - 2; - int cap_index = m_sequenced_download_threshold * 2; - TORRENT_ASSERT(m_piece_info[last_index].empty()); - if (last_index >= cap_index) + if (m_sequential_download >= 0) { - TORRENT_ASSERT(pushed_out_index == cap_index - 1 - || m_piece_info[cap_index - 1].empty()); - m_piece_info[cap_index].swap(m_piece_info[cap_index - 2]); - if (cap_index == pushed_out_index) - pushed_out_index = cap_index - 2; + --m_seeds; + return; } - // first is the vector that were - // bumped down to 0. The should always be moved - // since they have to be removed or reinserted - std::vector().swap(m_piece_info.front()); + if (m_seeds > 0) + { + --m_seeds; + if (m_seeds == 0) + { + // when m_seeds is decreased from 1 to 0 + // we may have to remove pieces that previously + // didn't have any peers + m_dirty = true; + } + return; + } for (std::vector::iterator i = m_piece_map.begin() , end(m_piece_map.end()); i != end; ++i) { - int prev_prio = i->priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prev_prio < int(m_piece_info.size())); - TORRENT_ASSERT(pushed_out_index < int(m_piece_info.size())); TORRENT_ASSERT(i->peer_count > 0); --i->peer_count; - // if the assumption that the priority would - // decrease by 2 when decreasing the availability - // by one isn't true for this particular piece, correct it. - // that assumption is true for all pieces with priority 0 or 1 - if (prev_prio == 0) - { - TORRENT_ASSERT(i->priority(m_sequenced_download_threshold) == 0); - continue; - } - - int new_prio = i->priority(m_sequenced_download_threshold); - if (prev_prio == cap_index) - { - if (new_prio == cap_index) continue; - prev_prio += 2; - } - else if (new_prio == prev_prio - 2) - { - continue; - } - else if (prev_prio == 2) - { - // if this piece was pushed down to priority 0, it was - // removed - TORRENT_ASSERT(new_prio > 0); - add(i - m_piece_map.begin()); - continue; - } - else if (prev_prio == 1) - { - // if this piece was one of the vectors that was pushed to the - // top, adjust the prev_prio to point to that vector, so that - // the pieces are moved from there - prev_prio = pushed_out_index + 2; - } - move(prev_prio - 2, i->index); } + + m_dirty = true; } - void piece_picker::inc_refcount(int i) + void piece_picker::inc_refcount(int index) { -// TORRENT_PIECE_PICKER_INVARIANT_CHECK; - TORRENT_ASSERT(i >= 0); - TORRENT_ASSERT(i < (int)m_piece_map.size()); - TORRENT_ASSERT(m_files_checked_called); + TORRENT_PIECE_PICKER_INVARIANT_CHECK; - piece_pos& p = m_piece_map[i]; - int index = p.index; - int prev_priority = p.priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prev_priority < int(m_piece_info.size())); - - TORRENT_ASSERT(p.peer_count < piece_pos::max_peer_count); - p.peer_count++; - TORRENT_ASSERT(p.peer_count != 0); - - // if we have the piece or if it's filtered - // we don't have to move any entries in the piece_info vector - if (p.priority(m_sequenced_download_threshold) == prev_priority) return; - - if (prev_priority == 0) + piece_pos& p = m_piece_map[index]; + if (m_sequential_download >= 0) { - add(i); + ++p.peer_count; + return; } + + int prev_priority = p.priority(this); + ++p.peer_count; + if (m_dirty) return; + int new_priority = p.priority(this); + if (prev_priority == new_priority) return; + if (prev_priority == -1) + add(index); else + update(prev_priority, p.index); + } + + void piece_picker::dec_refcount(int index) + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + + piece_pos& p = m_piece_map[index]; + if (m_sequential_download >= 0) { - move(prev_priority, index); + --p.peer_count; + return; } + int prev_priority = p.priority(this); + TORRENT_ASSERT(p.peer_count > 0); + --p.peer_count; + if (m_dirty) return; + if (prev_priority >= 0) update(prev_priority, p.index); + } + + void piece_picker::inc_refcount(std::vector const& bitmask) + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + TORRENT_ASSERT(bitmask.size() == m_piece_map.size()); + + int index = 0; + bool updated = false; + for (std::vector::const_iterator i = bitmask.begin() + , end(bitmask.end()); i != end; ++i, ++index) + { + if (*i) + { + ++m_piece_map[index].peer_count; + updated = true; + } + } + + if (updated && m_sequential_download == -1) m_dirty = true; + } + + void piece_picker::dec_refcount(std::vector const& bitmask) + { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + TORRENT_ASSERT(bitmask.size() == m_piece_map.size()); + + int index = 0; + bool updated = false; + for (std::vector::const_iterator i = bitmask.begin() + , end(bitmask.end()); i != end; ++i, ++index) + { + if (*i) + { + --m_piece_map[index].peer_count; + updated = true; + } + } + + if (updated && m_sequential_download == -1) m_dirty = true; + } + + void piece_picker::update_pieces() const + { + TORRENT_ASSERT(m_dirty); + TORRENT_ASSERT(m_sequential_download == -1); + if (m_priority_boundries.empty()) m_priority_boundries.resize(1, 0); +#ifdef TORRENT_PICKER_LOG + std::cout << "update_pieces" << std::endl; +#endif + std::fill(m_priority_boundries.begin(), m_priority_boundries.end(), 0); + for (std::vector::iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end; ++i) + { + int prio = i->priority(this); + if (prio == -1) continue; + if (prio >= int(m_priority_boundries.size())) + m_priority_boundries.resize(prio + 1, 0); + i->index = m_priority_boundries[prio]; + ++m_priority_boundries[prio]; + } + +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + + int index = 0; + for (std::vector::iterator i = m_priority_boundries.begin() + , end(m_priority_boundries.end()); i != end; ++i) + { + *i += index; + index = *i; + } + m_pieces.resize(index, 0); + +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif + + index = 0; + for (std::vector::iterator i = m_piece_map.begin() + , end(m_piece_map.end()); i != end; ++i, ++index) + { + piece_pos& p = *i; + int prio = p.priority(this); + if (prio == -1) continue; + int new_index = (prio == 0 ? 0 : m_priority_boundries[prio - 1]) + p.index; + m_pieces[new_index] = index; + } + + int start = 0; + for (std::vector::iterator i = m_priority_boundries.begin() + , end(m_priority_boundries.end()); i != end; ++i) + { + if (start == *i) continue; + std::random_shuffle(&m_pieces[0] + start, &m_pieces[0] + *i); + start = *i; + } + + index = 0; + for (std::vector::const_iterator i = m_pieces.begin() + , end(m_pieces.end()); i != end; ++i, ++index) + { + TORRENT_ASSERT(*i >= 0 && *i < int(m_piece_map.size())); + m_piece_map[*i].index = index; + } + + m_dirty = false; +#ifdef TORRENT_PICKER_LOG + print_pieces(); +#endif #ifndef NDEBUG -// integrity_check(); + check_invariant(); #endif - return; - } - - void piece_picker::dec_refcount(int i) - { -// TORRENT_PIECE_PICKER_INVARIANT_CHECK; - - TORRENT_ASSERT(m_files_checked_called); - TORRENT_ASSERT(i >= 0); - TORRENT_ASSERT(i < (int)m_piece_map.size()); - - piece_pos& p = m_piece_map[i]; - int prev_priority = p.priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prev_priority < int(m_piece_info.size())); - int index = p.index; - TORRENT_ASSERT(p.peer_count > 0); - - if (p.peer_count > 0) - p.peer_count--; - - if (p.priority(m_sequenced_download_threshold) == prev_priority) return; - - move(prev_priority, index); } // this is used to indicate that we succesfully have @@ -971,8 +1045,8 @@ namespace libtorrent piece_pos& p = m_piece_map[index]; int info_index = p.index; - int priority = p.priority(m_sequenced_download_threshold); - TORRENT_ASSERT(priority < int(m_piece_info.size())); + int priority = p.priority(this); + TORRENT_ASSERT(priority < int(m_priority_boundries.size())); if (p.downloading) { @@ -988,6 +1062,13 @@ namespace libtorrent TORRENT_ASSERT(std::find_if(m_downloads.begin(), m_downloads.end() , has_index(index)) == m_downloads.end()); + if (m_sequential_download == index) + { + ++m_sequential_download; + for (std::vector::const_iterator i = m_piece_map.begin() + m_sequential_download + , end(m_piece_map.end()); i != end && (i->have() || i->filtered()); + ++i, ++m_sequential_download); + } if (p.have()) return; if (p.filtered()) { @@ -996,12 +1077,12 @@ namespace libtorrent } ++m_num_have; p.set_have(); - if (priority == 0) return; - TORRENT_ASSERT(p.priority(m_sequenced_download_threshold) == 0); - move(priority, info_index); + if (priority == -1) return; + if (m_dirty) return; + remove(priority, info_index); + TORRENT_ASSERT(p.priority(this) == -1); } - bool piece_picker::set_piece_priority(int index, int new_piece_priority) { TORRENT_PIECE_PICKER_INVARIANT_CHECK; @@ -1015,8 +1096,8 @@ namespace libtorrent // if the priority isn't changed, don't do anything if (new_piece_priority == int(p.piece_priority)) return false; - int prev_priority = p.priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prev_priority < int(m_piece_info.size())); + int prev_priority = p.priority(this); + TORRENT_ASSERT(prev_priority < int(m_priority_boundries.size())); bool ret = false; if (new_piece_priority == piece_pos::filter_priority @@ -1039,18 +1120,20 @@ namespace libtorrent TORRENT_ASSERT(m_num_have_filtered >= 0); p.piece_priority = new_piece_priority; - int new_priority = p.priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prev_priority < int(m_piece_info.size())); + int new_priority = p.priority(this); - if (new_priority == prev_priority) return false; - - if (prev_priority == 0) + if (prev_priority == new_priority) return false; + + TORRENT_ASSERT(prev_priority < int(m_priority_boundries.size())); + + if (m_dirty) return ret; + if (prev_priority == -1) { add(index); } else { - move(prev_priority, p.index); + update(prev_priority, p.index); } return ret; } @@ -1115,7 +1198,9 @@ namespace libtorrent TORRENT_ASSERT(pieces.size() == m_piece_map.size()); TORRENT_ASSERT(m_files_checked_called); - TORRENT_ASSERT(m_piece_info.begin() != m_piece_info.end()); + TORRENT_ASSERT(!m_priority_boundries.empty() + || m_sequential_download >= 0 + || m_dirty); // this will be filled with blocks that we should not request // unless we can't find num_blocks among the other ones. @@ -1124,8 +1209,6 @@ namespace libtorrent // blocks belonging to a piece that others have // downloaded to std::vector backup_blocks; - // suggested pieces for each vector is put in this vector - std::vector suggested_bucket; const std::vector empty_vector; // When prefer_whole_pieces is set (usually set when downloading from @@ -1139,50 +1222,38 @@ namespace libtorrent if (num_blocks <= 0) return; - if (rarest_first) + if (!suggested_pieces.empty()) { - // this loop will loop from pieces with priority 1 and up - // until we either reach the end of the piece list or - // has filled the interesting_blocks with num_blocks - // blocks. + num_blocks = add_blocks(suggested_pieces, pieces + , interesting_blocks, num_blocks + , prefer_whole_pieces, peer, empty_vector); + if (num_blocks == 0) return; + } - // +1 is to ignore pieces that no peer has. The bucket with index 0 contains - // pieces that 0 other peers have. bucket will point to a bucket with - // pieces with the same priority. It will be iterated in priority - // order (high priority/rare pices first). The content of each - // bucket is randomized - for (std::vector >::const_iterator bucket - = m_piece_info.begin() + 1; num_blocks > 0 && bucket != m_piece_info.end(); - ++bucket) + if (m_sequential_download >= 0) + { + for (int i = m_sequential_download; + i < int(m_piece_map.size()) && num_blocks > 0; ++i) { - if (bucket->empty()) continue; - if (!suggested_pieces.empty()) + if (!can_pick(i, pieces)) continue; + int num_blocks_in_piece = blocks_in_piece(i); + if (prefer_whole_pieces == 0 && num_blocks_in_piece > num_blocks) + num_blocks_in_piece = num_blocks; + for (int j = 0; j < num_blocks_in_piece; ++j) { - int bucket_index = bucket - m_piece_info.begin(); - suggested_bucket.clear(); - for (std::vector::const_iterator i = suggested_pieces.begin() - , end(suggested_pieces.end()); i != end; ++i) - { - TORRENT_ASSERT(*i >= 0); - TORRENT_ASSERT(*i < int(m_piece_map.size())); - if (!can_pick(*i, pieces)) continue; - if (m_piece_map[*i].priority(m_sequenced_download_threshold) == bucket_index) - suggested_bucket.push_back(*i); - } - if (!suggested_bucket.empty()) - { - num_blocks = add_blocks(suggested_bucket, pieces - , interesting_blocks, num_blocks - , prefer_whole_pieces, peer, empty_vector); - if (num_blocks == 0) break; - } + interesting_blocks.push_back(piece_block(i, j)); + --num_blocks; } - num_blocks = add_blocks(*bucket, pieces - , interesting_blocks, num_blocks - , prefer_whole_pieces, peer, suggested_bucket); - TORRENT_ASSERT(num_blocks >= 0); } } + else if (rarest_first) + { + if (m_dirty) update_pieces(); + num_blocks = add_blocks(m_pieces, pieces + , interesting_blocks, num_blocks + , prefer_whole_pieces, peer, suggested_pieces); + TORRENT_ASSERT(num_blocks >= 0); + } else { // we're not using rarest first (only for the first @@ -1214,7 +1285,7 @@ namespace libtorrent for (int k = start; k < end; ++k) { TORRENT_ASSERT(m_piece_map[piece].downloading == false); - TORRENT_ASSERT(m_piece_map[k].priority(m_sequenced_download_threshold) > 0); + TORRENT_ASSERT(m_piece_map[k].priority(this) >= 0); int num_blocks_in_piece = blocks_in_piece(k); if (prefer_whole_pieces == 0 && num_blocks_in_piece > num_blocks) num_blocks_in_piece = num_blocks; @@ -1298,19 +1369,17 @@ namespace libtorrent TORRENT_ASSERT(*i < (int)m_piece_map.size()); // if the peer doesn't have the piece + // or if it's set to 0 priority // skip it - if (!pieces[*i]) continue; + if (!can_pick(*i, pieces)) continue; // ignore pieces found in the ignore list if (std::find(ignore.begin(), ignore.end(), *i) != ignore.end()) continue; - // skip the piece is the priority is 0 - TORRENT_ASSERT(m_piece_map[*i].priority(m_sequenced_download_threshold) > 0); - - int num_blocks_in_piece = blocks_in_piece(*i); - + TORRENT_ASSERT(m_piece_map[*i].priority(this) >= 0); TORRENT_ASSERT(m_piece_map[*i].downloading == 0); - TORRENT_ASSERT(m_piece_map[*i].priority(m_sequenced_download_threshold) > 0); + + int num_blocks_in_piece = blocks_in_piece(*i); // pick a new piece if (prefer_whole_pieces == 0) @@ -1327,7 +1396,7 @@ namespace libtorrent boost::tie(start, end) = expand_piece(*i, prefer_whole_pieces, pieces); for (int k = start; k < end; ++k) { - TORRENT_ASSERT(m_piece_map[k].priority(m_sequenced_download_threshold) > 0); + TORRENT_ASSERT(m_piece_map[k].priority(this) > 0); num_blocks_in_piece = blocks_in_piece(k); for (int j = 0; j < num_blocks_in_piece; ++j) { @@ -1638,12 +1707,9 @@ namespace libtorrent return i->info[block.block_index].state == block_info::state_finished; } - bool piece_picker::mark_as_downloading(piece_block block , void* peer, piece_state_t state) { - TORRENT_PIECE_PICKER_INVARIANT_CHECK; - TORRENT_ASSERT(block.piece_index >= 0); TORRENT_ASSERT(block.block_index >= 0); TORRENT_ASSERT(block.piece_index < (int)m_piece_map.size()); @@ -1653,11 +1719,14 @@ namespace libtorrent piece_pos& p = m_piece_map[block.piece_index]; if (p.downloading == 0) { - int prio = p.priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prio < int(m_piece_info.size())); + TORRENT_PIECE_PICKER_INVARIANT_CHECK; + int prio = p.priority(this); + TORRENT_ASSERT(prio < int(m_priority_boundries.size()) + || m_sequential_download >= 0 + || m_dirty); TORRENT_ASSERT(prio > 0); p.downloading = 1; - move(prio, p.index); + if (prio >= 0 && m_sequential_download == -1 && !m_dirty) update(prio, p.index); downloading_piece& dp = add_download_piece(); dp.state = state; @@ -1670,6 +1739,7 @@ namespace libtorrent } else { + TORRENT_PIECE_PICKER_INVARIANT_CHECK; std::vector::iterator i = std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index)); TORRENT_ASSERT(i != m_downloads.end()); @@ -1718,7 +1788,7 @@ namespace libtorrent std::vector::iterator j = avail.begin(); for (std::vector::const_iterator i = m_piece_map.begin() , end(m_piece_map.end()); i != end; ++i, ++j) - *j = i->peer_count; + *j = i->peer_count + m_seeds; } void piece_picker::mark_as_writing(piece_block block, void* peer) @@ -1768,11 +1838,11 @@ namespace libtorrent TORRENT_PIECE_PICKER_INVARIANT_CHECK; TORRENT_ASSERT(peer == 0); - int prio = p.priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prio < int(m_piece_info.size())); + int prio = p.priority(this); + TORRENT_ASSERT(prio < int(m_priority_boundries.size()) + || m_dirty); p.downloading = 1; - if (prio > 0) move(prio, p.index); - else TORRENT_ASSERT(p.priority(m_sequenced_download_threshold) == 0); + if (prio >= 0 && !m_dirty) update(prio, p.index); downloading_piece& dp = add_download_piece(); dp.state = none; @@ -1890,12 +1960,16 @@ namespace libtorrent { erase_download_piece(i); piece_pos& p = m_piece_map[block.piece_index]; - int prev_prio = p.priority(m_sequenced_download_threshold); - TORRENT_ASSERT(prev_prio < int(m_piece_info.size())); + int prev_prio = p.priority(this); + TORRENT_ASSERT(prev_prio < int(m_priority_boundries.size()) + || m_dirty); p.downloading = 0; - int prio = p.priority(m_sequenced_download_threshold); - if (prev_prio == 0 && prio > 0) add(block.piece_index); - else if (prio > 0) move(prio, p.index); + if (m_sequential_download == -1 && !m_dirty) + { + int prio = p.priority(this); + if (prev_prio == -1 && prio >= 0) add(block.piece_index); + else if (prev_prio >= 0) update(prev_prio, p.index); + } TORRENT_ASSERT(std::find_if(m_downloads.begin(), m_downloads.end() , has_index(block.piece_index)) == m_downloads.end()); diff --git a/src/torrent.cpp b/src/torrent.cpp index 8ef39c7c6..80308a889 100755 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -187,7 +187,7 @@ namespace libtorrent , m_failed_trackers(0) , m_time_scaler(0) , m_num_pieces(0) - , m_sequenced_download_threshold(0) + , m_sequential_download(false) , m_got_tracker_response(false) , m_ratio(0.f) , m_total_failed_bytes(0) @@ -249,7 +249,7 @@ namespace libtorrent , m_failed_trackers(0) , m_time_scaler(0) , m_num_pieces(0) - , m_sequenced_download_threshold(0) + , m_sequential_download(false) , m_got_tracker_response(false) , m_ratio(0.f) , m_total_failed_bytes(0) @@ -1598,16 +1598,12 @@ namespace libtorrent } else { - // if we're a seed, we don't keep track of piece availability - if (!is_seed()) + if (m_picker.get()) { const std::vector& pieces = p->get_bitfield(); - - for (std::vector::const_iterator i = pieces.begin(); - i != pieces.end(); ++i) - { - if (*i) peer_lost(static_cast(i - pieces.begin())); - } + TORRENT_ASSERT(std::count(pieces.begin(), pieces.end(), true) + < int(pieces.size())); + m_picker->dec_refcount(pieces); } } } @@ -2182,24 +2178,26 @@ namespace libtorrent if (m_ses.m_connections.find(p) == m_ses.m_connections.end()) { - throw protocol_error("peer is not properly constructed"); + p->disconnect("peer is not properly constructed"); + return; } if (m_ses.is_aborted()) { - throw protocol_error("session is closing"); + p->disconnect("session is closing"); + return; } if (int(m_connections.size()) >= m_max_connections) { - throw protocol_error("reached connection limit"); + p->disconnect("reached connection limit"); + return; } +#ifndef BOOST_NO_EXCEPTIONS try { - // if new_connection throws, we have to remove the - // it from the list. - +#endif #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) @@ -2210,6 +2208,7 @@ namespace libtorrent #endif if (!m_policy.new_connection(*p)) return; +#ifndef BOOST_NO_EXCEPTIONS } catch (std::exception& e) { @@ -2217,13 +2216,18 @@ namespace libtorrent (*m_ses.m_logger) << time_now_string() << " CLOSING CONNECTION " << p->remote() << " policy::new_connection threw: " << e.what() << "\n"; #endif - throw; + p->disconnect(e.what()); + return; } +#endif TORRENT_ASSERT(m_connections.find(p) == m_connections.end()); peer_iterator ci = m_connections.insert(p).first; - TORRENT_ASSERT(p->remote() == p->get_socket()->remote_endpoint()); - #ifndef NDEBUG + asio::error_code ec; + TORRENT_ASSERT(p->remote() == p->get_socket()->remote_endpoint(ec) || ec); +#endif + +#if !defined NDEBUG && !defined TORRENT_DISABLE_INVARIANT_CHECKS m_policy.check_invariant(); #endif } @@ -2554,8 +2558,8 @@ namespace libtorrent // against its hashes. std::vector verify_pieces; m_picker->files_checked(m_have_pieces, unfinished_pieces, verify_pieces); - if (m_sequenced_download_threshold > 0) - picker().set_sequenced_download_threshold(m_sequenced_download_threshold); + if (m_sequential_download) + picker().sequential_download(m_sequential_download); while (!verify_pieces.empty()) { int piece = verify_pieces.back(); @@ -2780,15 +2784,15 @@ namespace libtorrent } #endif - void torrent::set_sequenced_download_threshold(int threshold) + void torrent::set_sequential_download(bool sd) { if (has_picker()) { - picker().set_sequenced_download_threshold(threshold); + picker().sequential_download(sd); } else { - m_sequenced_download_threshold = threshold; + m_sequential_download = sd; } } @@ -3060,7 +3064,7 @@ namespace libtorrent m_storage->async_hash(piece_index, bind(&torrent::on_piece_verified , shared_from_this(), _1, _2, f)); -#ifndef NDEBUG +#if !defined NDEBUG && !defined TORRENT_DISABLE_INVARIANT_CHECKS check_invariant(); #endif } diff --git a/src/torrent_handle.cpp b/src/torrent_handle.cpp index 635390537..de3b23a07 100755 --- a/src/torrent_handle.cpp +++ b/src/torrent_handle.cpp @@ -335,10 +335,10 @@ namespace libtorrent return torrent_status(); } - void torrent_handle::set_sequenced_download_threshold(int threshold) const + void torrent_handle::set_sequential_download(bool sd) const { INVARIANT_CHECK; - TORRENT_FORWARD(set_sequenced_download_threshold(threshold)); + TORRENT_FORWARD(set_sequential_download(sd)); } std::string torrent_handle::name() const diff --git a/test/test_piece_picker.cpp b/test/test_piece_picker.cpp index 8498720fe..e31d559ed 100644 --- a/test/test_piece_picker.cpp +++ b/test/test_piece_picker.cpp @@ -156,6 +156,17 @@ void print_pick(std::vector const& picked) std::cout << std::endl; } +int test_pick(boost::shared_ptr const& p) +{ + std::vector picked; + const std::vector empty_vector; + p->pick_pieces(string2vec("*******"), picked, 1, false, 0, piece_picker::fast, true, false, empty_vector); + print_pick(picked); + TEST_CHECK(verify_pick(p, picked)); + TEST_CHECK(int(picked.size()) == 1); + return picked[0].piece_index; +} + int test_main() { @@ -236,7 +247,7 @@ int test_main() TEST_CHECK(picked.front().piece_index == 0); // ======================================================== - +/* // test sequenced download p = setup_picker("7654321", " ", "", ""); picked.clear(); @@ -283,7 +294,7 @@ int test_main() TEST_CHECK(int(picked.size()) == 6 * blocks_per_piece); for (int i = 0; i < 6 * blocks_per_piece && i < int(picked.size()); ++i) TEST_CHECK(picked[i].piece_index == i / blocks_per_piece); - +*/ // ======================================================== // test piece priorities @@ -402,9 +413,30 @@ int test_main() dc = p->distributed_copies(); std::cout << "distributed copies: " << dc << std::endl; TEST_CHECK(fabs(dc - (1.f + 5.f / 7.f)) < 0.01f); - + // ======================================================== + + // test inc_ref and dec_ref + p = setup_picker("1233333", " * ", "", ""); + TEST_CHECK(test_pick(p) == 0); + + p->dec_refcount(0); + TEST_CHECK(test_pick(p) == 1); + + p->dec_refcount(4); + p->dec_refcount(4); + TEST_CHECK(test_pick(p) == 4); + + // decrease refcount on something that's not in the piece list + p->dec_refcount(5); + p->inc_refcount(5); + p->inc_refcount(0); + p->dec_refcount(4); + TEST_CHECK(test_pick(p) == 0); + +// ======================================================== +/* // test have_all and have_none, with a sequenced download threshold p = setup_picker("1233333", "* ", "", ""); p->set_sequenced_download_threshold(3); @@ -432,7 +464,7 @@ int test_main() TEST_CHECK(picked[1 * blocks_per_piece].piece_index == 4); TEST_CHECK(picked[2 * blocks_per_piece].piece_index == 5); TEST_CHECK(picked[3 * blocks_per_piece].piece_index == 6); - +*/ // ======================================================== // test unverified_blocks, marking blocks and get_downloader