rewrote the piece picker to be more cpu and memory efficient. replaces the concept of sequential-download-threshold with just a sequential download settings

This commit is contained in:
Arvid Norberg
2008-01-31 17:52:29 +00:00
parent 3562c3e646
commit c798ab30e1
11 changed files with 822 additions and 656 deletions

View File

@@ -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

View File

@@ -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()

View File

@@ -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()

View File

@@ -133,7 +133,8 @@ namespace libtorrent
void get_availability(std::vector<int>& 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<int>& 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<bool> 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<bool> 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<piece_block> const& picked
, std::vector<bool> 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<bool> const& bitmask) const;
std::pair<int, int> expand_piece(int piece, int whole_pieces
, std::vector<bool> 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<downloading_piece>::iterator dp);
downloading_piece& add_download_piece();
void erase_download_piece(std::vector<downloading_piece>::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<std::vector<int> > 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<int> 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<int> 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<piece_pos> m_piece_map;
mutable std::vector<piece_pos> 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

View File

@@ -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<bool> 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

View File

@@ -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;

View File

@@ -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,50 +1103,32 @@ 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);
}
}
}
m_have_piece = bitfield;
m_num_pieces = num_pieces;
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;
}
}
}
}
// -----------------------------
// ---------- REQUEST ----------
@@ -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<socket_type> 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<torrent> 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<torrent> 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

File diff suppressed because it is too large Load Diff

View File

@@ -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<bool>& pieces = p->get_bitfield();
for (std::vector<bool>::const_iterator i = pieces.begin();
i != pieces.end(); ++i)
{
if (*i) peer_lost(static_cast<int>(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<int> 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
}

View File

@@ -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

View File

@@ -156,6 +156,17 @@ void print_pick(std::vector<piece_block> const& picked)
std::cout << std::endl;
}
int test_pick(boost::shared_ptr<piece_picker> const& p)
{
std::vector<piece_block> picked;
const std::vector<int> 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
@@ -405,6 +416,27 @@ int test_main()
// ========================================================
// 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