improve piece_deadline/streaming
This commit is contained in:
parent
4c7e24e9c6
commit
3192e59a1a
|
@ -1,3 +1,4 @@
|
|||
* improve piece_deadline/streaming
|
||||
* honor pieces with priority 7 in sequential download mode
|
||||
* simplified building python bindings
|
||||
* make ignore_non_routers more forgiving in the case there are no UPnP
|
||||
|
|
|
@ -510,7 +510,9 @@ namespace libtorrent
|
|||
|
||||
bool can_request_time_critical() const;
|
||||
|
||||
void make_time_critical(piece_block const& block);
|
||||
// returns true if the specified block was actually made time-critical.
|
||||
// if the block was already time-critical, it returns false.
|
||||
bool make_time_critical(piece_block const& block);
|
||||
|
||||
// adds a block to the request queue
|
||||
// returns true if successful, false otherwise
|
||||
|
|
|
@ -151,7 +151,11 @@ namespace libtorrent
|
|||
// have affinity to pieces with the same speed category
|
||||
speed_affinity = 32,
|
||||
// ignore the prefer_whole_pieces parameter
|
||||
ignore_whole_pieces = 64
|
||||
ignore_whole_pieces = 64,
|
||||
// treat pieces with priority 6 and below as filtered
|
||||
// to trigger end-game mode until all prio 7 pieces are
|
||||
// completed
|
||||
time_critical_mode = 128
|
||||
};
|
||||
|
||||
struct downloading_piece
|
||||
|
|
|
@ -80,6 +80,8 @@ POSSIBILITY OF SUCH DAMAGE.
|
|||
#include "libtorrent/peer_connection.hpp"
|
||||
#endif
|
||||
|
||||
//#define TORRENT_DEBUG_STREAMING 1
|
||||
|
||||
namespace libtorrent
|
||||
{
|
||||
class http_parser;
|
||||
|
@ -104,6 +106,29 @@ namespace libtorrent
|
|||
struct piece_checker_data;
|
||||
}
|
||||
|
||||
struct time_critical_piece
|
||||
{
|
||||
// when this piece was first requested
|
||||
ptime first_requested;
|
||||
// when this piece was last requested
|
||||
ptime last_requested;
|
||||
// by what time we want this piece
|
||||
ptime deadline;
|
||||
// 1 = send alert with piece data when available
|
||||
int flags;
|
||||
// how many peers it's been requested from
|
||||
int peers;
|
||||
// the piece index
|
||||
int piece;
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
// the number of multiple requests are allowed
|
||||
// to blocks still not downloaded (debugging only)
|
||||
int timed_out;
|
||||
#endif
|
||||
bool operator<(time_critical_piece const& rhs) const
|
||||
{ return deadline < rhs.deadline; }
|
||||
};
|
||||
|
||||
// a torrent is a class that holds information
|
||||
// for a specific download. It updates itself against
|
||||
// the tracker
|
||||
|
@ -874,6 +899,9 @@ namespace libtorrent
|
|||
boost::asio::ssl::context* ssl_ctx() const { return m_ssl_ctx.get(); }
|
||||
#endif
|
||||
|
||||
int num_time_critical_pieces() const
|
||||
{ return m_time_critical_pieces.size(); }
|
||||
|
||||
private:
|
||||
|
||||
void on_files_deleted(int ret, disk_io_job const& j);
|
||||
|
@ -1013,24 +1041,6 @@ namespace libtorrent
|
|||
std::vector<announce_entry> m_trackers;
|
||||
// this is an index into m_trackers
|
||||
|
||||
struct time_critical_piece
|
||||
{
|
||||
// when this piece was first requested
|
||||
ptime first_requested;
|
||||
// when this piece was last requested
|
||||
ptime last_requested;
|
||||
// by what time we want this piece
|
||||
ptime deadline;
|
||||
// 1 = send alert with piece data when available
|
||||
int flags;
|
||||
// how many peers it's been requested from
|
||||
int peers;
|
||||
// the piece index
|
||||
int piece;
|
||||
bool operator<(time_critical_piece const& rhs) const
|
||||
{ return deadline < rhs.deadline; }
|
||||
};
|
||||
|
||||
// this list is sorted by time_critical_piece::deadline
|
||||
std::deque<time_critical_piece> m_time_critical_pieces;
|
||||
|
||||
|
|
|
@ -838,6 +838,11 @@ namespace libtorrent
|
|||
TORRENT_ASSERT(t);
|
||||
if (!t) return 0;
|
||||
|
||||
if (t->num_time_critical_pieces() > 0)
|
||||
{
|
||||
ret |= piece_picker::time_critical_mode;
|
||||
}
|
||||
|
||||
if (t->is_sequential_download())
|
||||
{
|
||||
ret |= piece_picker::sequential;
|
||||
|
@ -963,12 +968,44 @@ namespace libtorrent
|
|||
|
||||
time_duration peer_connection::download_queue_time(int extra_bytes) const
|
||||
{
|
||||
int rate = m_statistics.transfer_rate(stat::download_payload)
|
||||
+ m_statistics.transfer_rate(stat::download_protocol);
|
||||
// avoid division by zero
|
||||
if (rate < 50) rate = 50;
|
||||
boost::shared_ptr<torrent> t = m_torrent.lock();
|
||||
TORRENT_ASSERT(t);
|
||||
|
||||
int rate = 0;
|
||||
|
||||
// if we haven't received any data recently, the current download rate
|
||||
// is not representative
|
||||
if (time_now() - m_last_piece > seconds(30) && m_download_rate_peak > 0)
|
||||
{
|
||||
rate = m_download_rate_peak;
|
||||
}
|
||||
else if (time_now() - m_last_unchoked < seconds(5)
|
||||
&& m_statistics.total_payload_upload() < 2 * 0x4000)
|
||||
{
|
||||
// if we're have only been unchoked for a short period of time,
|
||||
// we don't know what rate we can get from this peer. Instead of assuming
|
||||
// the lowest possible rate, assume the average.
|
||||
|
||||
// TODO: this should only be peers we're trying to download from
|
||||
int peers_with_requests = m_ses.num_connections();
|
||||
// avoid division by 0
|
||||
if (peers_with_requests == 0) peers_with_requests = 1;
|
||||
|
||||
rate = m_ses.m_stat.transfer_rate(stat::download_payload) / peers_with_requests;
|
||||
}
|
||||
else
|
||||
{
|
||||
// current download rate in bytes per seconds
|
||||
rate = m_statistics.transfer_rate(stat::download_payload)
|
||||
+ m_statistics.transfer_rate(stat::download_protocol);
|
||||
}
|
||||
|
||||
// avoid division by zero
|
||||
if (rate < 50) rate = 50;
|
||||
|
||||
// average of current rate and peak
|
||||
// rate = (rate + m_download_rate_peak) / 2;
|
||||
|
||||
return seconds((m_outstanding_bytes + m_queued_time_critical * t->block_size()) / rate);
|
||||
}
|
||||
|
||||
|
@ -2891,17 +2928,17 @@ namespace libtorrent
|
|||
TORRENT_ASSERT(t);
|
||||
if (t->upload_mode()) return false;
|
||||
|
||||
// ignore snubbed peers, since they're not likely to return pieces in a timely
|
||||
// manner anyway
|
||||
// ignore snubbed peers, since they're not likely to return pieces in a
|
||||
// timely manner anyway
|
||||
if (m_snubbed) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void peer_connection::make_time_critical(piece_block const& block)
|
||||
bool peer_connection::make_time_critical(piece_block const& block)
|
||||
{
|
||||
std::vector<pending_block>::iterator rit = std::find_if(m_request_queue.begin()
|
||||
, m_request_queue.end(), has_block(block));
|
||||
if (rit == m_request_queue.end()) return;
|
||||
if (rit == m_request_queue.end()) return false;
|
||||
#if TORRENT_USE_ASSERTS
|
||||
boost::shared_ptr<torrent> t = m_torrent.lock();
|
||||
TORRENT_ASSERT(t);
|
||||
|
@ -2909,11 +2946,12 @@ namespace libtorrent
|
|||
TORRENT_ASSERT(t->picker().is_requested(block));
|
||||
#endif
|
||||
// ignore it if it's already time critical
|
||||
if (rit - m_request_queue.begin() < m_queued_time_critical) return;
|
||||
if (rit - m_request_queue.begin() < m_queued_time_critical) return false;
|
||||
pending_block b = *rit;
|
||||
m_request_queue.erase(rit);
|
||||
m_request_queue.insert(m_request_queue.begin() + m_queued_time_critical, b);
|
||||
++m_queued_time_critical;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool peer_connection::add_request(piece_block const& block, int flags)
|
||||
|
@ -2959,11 +2997,14 @@ namespace libtorrent
|
|||
state = piece_picker::slow;
|
||||
}
|
||||
|
||||
if (flags & req_busy)
|
||||
if ((flags & req_busy) && !(flags & req_time_critical))
|
||||
{
|
||||
// this block is busy (i.e. it has been requested
|
||||
// from another peer already). Only allow one busy
|
||||
// request in the pipeline at the time
|
||||
// this rule does not apply to time critical pieces,
|
||||
// in which case we are allowed to pick more than one
|
||||
// busy blocks
|
||||
for (std::vector<pending_block>::const_iterator i = m_download_queue.begin()
|
||||
, end(m_download_queue.end()); i != end; ++i)
|
||||
{
|
||||
|
@ -4045,7 +4086,7 @@ namespace libtorrent
|
|||
return;
|
||||
}
|
||||
|
||||
int download_rate = statistics().download_rate();
|
||||
int download_rate = statistics().download_payload_rate();
|
||||
|
||||
// calculate the desired download queue size
|
||||
const int queue_time = m_ses.settings().request_queue_time;
|
||||
|
|
|
@ -153,7 +153,6 @@ namespace libtorrent
|
|||
std::vector<downloading_piece>::const_iterator piece = find_dl_piece(index);
|
||||
TORRENT_ASSERT(piece != m_downloads.end());
|
||||
st = *piece;
|
||||
st.info = 0;
|
||||
return;
|
||||
}
|
||||
st.info = 0;
|
||||
|
@ -1547,6 +1546,10 @@ namespace libtorrent
|
|||
for (std::vector<downloading_piece>::const_iterator i = m_downloads.begin()
|
||||
, end(m_downloads.end()); i != end; ++i)
|
||||
{
|
||||
// in time critical mode, only pick prio 7 pieces
|
||||
if ((options & time_critical_mode) && piece_priority(i->index) != 7)
|
||||
continue;
|
||||
|
||||
if (!is_piece_free(i->index, pieces)) continue;
|
||||
if (m_piece_map[i->index].full
|
||||
&& backup_blocks.size() >= num_blocks
|
||||
|
@ -1573,6 +1576,10 @@ namespace libtorrent
|
|||
for (std::vector<int>::const_iterator i = suggested_pieces.begin();
|
||||
i != suggested_pieces.end(); ++i)
|
||||
{
|
||||
// in time critical mode, only pick prio 7 pieces
|
||||
if ((options & time_critical_mode) && piece_priority(*i) != 7)
|
||||
continue;
|
||||
|
||||
if (!is_piece_free(*i, pieces)) continue;
|
||||
num_blocks = add_blocks(*i, pieces
|
||||
, interesting_blocks, backup_blocks
|
||||
|
@ -1600,6 +1607,9 @@ namespace libtorrent
|
|||
if (num_blocks <= 0) return;
|
||||
}
|
||||
|
||||
// in time critical mode, only pick prio 7 pieces
|
||||
if ((options & time_critical_mode) == 0)
|
||||
{
|
||||
if (options & reverse)
|
||||
{
|
||||
for (int i = m_reverse_cursor - 1; i >= m_cursor; --i)
|
||||
|
@ -1631,12 +1641,17 @@ namespace libtorrent
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (options & rarest_first)
|
||||
{
|
||||
if (m_dirty) update_pieces();
|
||||
TORRENT_ASSERT(!m_dirty);
|
||||
|
||||
if (options & reverse)
|
||||
// in time critical mode, we're only allowed to pick prio 7
|
||||
// pieces. This is why reverse mode is disabled when we're in
|
||||
// time-critical mode, because all prio 7 pieces are at the front
|
||||
// of the list
|
||||
if ((options & reverse) && (options & time_critical_mode) == 0)
|
||||
{
|
||||
// it's a bit complicated in order to always prioritize
|
||||
// partial pieces, and respect priorities. Every chunk
|
||||
|
@ -1673,6 +1688,13 @@ namespace libtorrent
|
|||
for (std::vector<int>::const_iterator i = m_pieces.begin();
|
||||
i != m_pieces.end(); ++i)
|
||||
{
|
||||
// in time critical mode, only pick prio 7 pieces
|
||||
// it's safe to break here because in this mode we
|
||||
// pick pieces in priority order. Once we hit a lower priority
|
||||
// piece, we won't encounter any more prio 7 ones
|
||||
if ((options & time_critical_mode) && piece_priority(*i) != 7)
|
||||
break;
|
||||
|
||||
if (!is_piece_free(*i, pieces)) continue;
|
||||
num_blocks = add_blocks(*i, pieces
|
||||
, interesting_blocks, backup_blocks
|
||||
|
@ -1683,8 +1705,25 @@ namespace libtorrent
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (options & time_critical_mode)
|
||||
{
|
||||
// if we're in time-critical mode, we are only allowed to pick
|
||||
// prio 7 pieces.
|
||||
for (std::vector<int>::const_iterator i = m_pieces.begin();
|
||||
i != m_pieces.end() && piece_priority(*i) == 7; ++i)
|
||||
{
|
||||
if (!is_piece_free(*i, pieces)) continue;
|
||||
num_blocks = add_blocks(*i, pieces
|
||||
, interesting_blocks, backup_blocks
|
||||
, backup_blocks2, num_blocks
|
||||
, prefer_whole_pieces, peer, suggested_pieces
|
||||
, speed, options);
|
||||
if (num_blocks <= 0) return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// we're not using rarest first (only for the first
|
||||
// bucket, since that's where the currently downloading
|
||||
// pieces are)
|
||||
|
@ -1808,6 +1847,9 @@ namespace libtorrent
|
|||
if (!pieces[i->index]) continue;
|
||||
if (piece_priority(i->index) == 0) continue;
|
||||
|
||||
if ((options & time_critical_mode) && piece_priority(i->index) != 7)
|
||||
continue;
|
||||
|
||||
int num_blocks_in_piece = blocks_in_piece(i->index);
|
||||
for (int j = 0; j < num_blocks_in_piece; ++j)
|
||||
{
|
||||
|
@ -1838,7 +1880,7 @@ namespace libtorrent
|
|||
}
|
||||
}
|
||||
|
||||
if (interesting_blocks.empty())
|
||||
if (interesting_blocks.empty() && !(options & time_critical_mode))
|
||||
{
|
||||
// print_pieces();
|
||||
for (int i = 0; i < num_pieces(); ++i)
|
||||
|
|
|
@ -244,7 +244,17 @@ namespace libtorrent
|
|||
|
||||
TORRENT_ASSERT(t.valid_metadata());
|
||||
TORRENT_ASSERT(c.peer_info_struct() != 0 || c.type() != peer_connection::bittorrent_connection);
|
||||
int num_requests = c.desired_queue_size()
|
||||
|
||||
bool time_critical_mode = t.num_time_critical_pieces() > 0;
|
||||
|
||||
int desired_queue_size = c.desired_queue_size();
|
||||
|
||||
// in time critical mode, only have 1 outstanding request at a time
|
||||
// via normal requests
|
||||
if (time_critical_mode)
|
||||
desired_queue_size = (std::min)(1, desired_queue_size);
|
||||
|
||||
int num_requests = desired_queue_size
|
||||
- (int)c.download_queue().size()
|
||||
- (int)c.request_queue().size();
|
||||
|
||||
|
@ -262,7 +272,7 @@ namespace libtorrent
|
|||
|
||||
int prefer_whole_pieces = c.prefer_whole_pieces();
|
||||
|
||||
if (prefer_whole_pieces == 0)
|
||||
if (prefer_whole_pieces == 0 && !time_critical_mode)
|
||||
{
|
||||
prefer_whole_pieces = c.statistics().download_payload_rate()
|
||||
* t.settings().whole_pieces_threshold
|
||||
|
@ -331,9 +341,11 @@ namespace libtorrent
|
|||
// and we're not strictly speaking in end-game mode yet
|
||||
// also, if we already have at least one outstanding
|
||||
// request, we shouldn't pick any busy pieces either
|
||||
bool dont_pick_busy_blocks = (ses.m_settings.strict_end_game_mode
|
||||
// in time critical mode, it's OK to request busy blocks
|
||||
bool dont_pick_busy_blocks = ((ses.m_settings.strict_end_game_mode
|
||||
&& p.num_downloading_pieces() < p.num_want_left())
|
||||
|| dq.size() + rq.size() > 0;
|
||||
|| dq.size() + rq.size() > 0)
|
||||
&& !time_critical_mode;
|
||||
|
||||
// this is filled with an interesting piece
|
||||
// that some other peer is currently downloading
|
||||
|
@ -348,6 +360,13 @@ namespace libtorrent
|
|||
|
||||
if (prefer_whole_pieces == 0 && num_requests <= 0) break;
|
||||
|
||||
if (time_critical_mode && p.piece_priority(i->piece_index) != 7)
|
||||
{
|
||||
// assume the subsequent pieces are not prio 7 and
|
||||
// be done
|
||||
break;
|
||||
}
|
||||
|
||||
int num_block_requests = p.num_peers(*i);
|
||||
if (num_block_requests > 0)
|
||||
{
|
||||
|
|
549
src/torrent.cpp
549
src/torrent.cpp
|
@ -78,6 +78,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
|||
#include "libtorrent/gzip.hpp" // for inflate_gzip
|
||||
#include "libtorrent/random.hpp"
|
||||
#include "libtorrent/string_util.hpp" // for allocate_string_copy
|
||||
#include "libtorrent/alloca.hpp"
|
||||
|
||||
#ifdef TORRENT_USE_OPENSSL
|
||||
#include "libtorrent/ssl_stream.hpp"
|
||||
|
@ -3802,8 +3803,8 @@ namespace libtorrent
|
|||
--i;
|
||||
}
|
||||
// just in case this piece had priority 0
|
||||
if (m_picker->piece_priority(piece) == 0)
|
||||
m_picker->set_piece_priority(piece, 1);
|
||||
int prev_prio = m_picker->piece_priority(piece);
|
||||
m_picker->set_piece_priority(piece, 7);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -3819,8 +3820,8 @@ namespace libtorrent
|
|||
m_time_critical_pieces.insert(i, p);
|
||||
|
||||
// just in case this piece had priority 0
|
||||
if (m_picker->piece_priority(piece) == 0)
|
||||
m_picker->set_piece_priority(piece, 1);
|
||||
int prev_prio = m_picker->piece_priority(piece);
|
||||
m_picker->set_piece_priority(piece, 7);
|
||||
|
||||
piece_picker::downloading_piece pi;
|
||||
m_picker->piece_info(piece, pi);
|
||||
|
@ -3875,9 +3876,9 @@ namespace libtorrent
|
|||
{
|
||||
int diff = abs(int(dl_time - m_average_piece_time));
|
||||
if (m_piece_time_deviation == 0) m_piece_time_deviation = diff;
|
||||
else m_piece_time_deviation = (m_piece_time_deviation * 6 + diff * 4) / 10;
|
||||
else m_piece_time_deviation = (m_piece_time_deviation * 9 + diff) / 10;
|
||||
|
||||
m_average_piece_time = (m_average_piece_time * 6 + dl_time * 4) / 10;
|
||||
m_average_piece_time = (m_average_piece_time * 9 + dl_time) / 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3887,6 +3888,7 @@ namespace libtorrent
|
|||
m_ses.m_alerts.post_alert(read_piece_alert(
|
||||
get_handle(), piece, error_code(boost::system::errc::operation_canceled, get_system_category())));
|
||||
}
|
||||
if (has_picker()) m_picker->set_piece_priority(piece, 1);
|
||||
m_time_critical_pieces.erase(i);
|
||||
return;
|
||||
}
|
||||
|
@ -8212,9 +8214,356 @@ namespace libtorrent
|
|||
m_stat += s;
|
||||
}
|
||||
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
char const* esc(char const* code)
|
||||
{
|
||||
// this is a silly optimization
|
||||
// to avoid copying of strings
|
||||
enum { num_strings = 200 };
|
||||
static char buf[num_strings][20];
|
||||
static int round_robin = 0;
|
||||
char* ret = buf[round_robin];
|
||||
++round_robin;
|
||||
if (round_robin >= num_strings) round_robin = 0;
|
||||
ret[0] = '\033';
|
||||
ret[1] = '[';
|
||||
int i = 2;
|
||||
int j = 0;
|
||||
while (code[j]) ret[i++] = code[j++];
|
||||
ret[i++] = 'm';
|
||||
ret[i++] = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int peer_index(libtorrent::tcp::endpoint addr
|
||||
, std::vector<libtorrent::peer_info> const& peers)
|
||||
{
|
||||
using namespace libtorrent;
|
||||
std::vector<peer_info>::const_iterator i = std::find_if(peers.begin()
|
||||
, peers.end(), boost::bind(&peer_info::ip, _1) == addr);
|
||||
if (i == peers.end()) return -1;
|
||||
|
||||
return i - peers.begin();
|
||||
}
|
||||
|
||||
void print_piece(libtorrent::partial_piece_info* pp
|
||||
, std::vector<libtorrent::peer_info> const& peers
|
||||
, std::vector<time_critical_piece> const& time_critical)
|
||||
{
|
||||
using namespace libtorrent;
|
||||
|
||||
ptime now = time_now_hires();
|
||||
|
||||
float deadline = 0.f;
|
||||
float last_request = 0.f;
|
||||
int timed_out = -1;
|
||||
|
||||
int piece = pp->piece_index;
|
||||
std::vector<time_critical_piece>::const_iterator i
|
||||
= std::find_if(time_critical.begin(), time_critical.end()
|
||||
, boost::bind(&time_critical_piece::piece, _1) == piece);
|
||||
if (i != time_critical.end())
|
||||
{
|
||||
deadline = total_milliseconds(i->deadline - now) / 1000.f;
|
||||
last_request = total_milliseconds(now - i->last_requested) / 1000.f;
|
||||
timed_out = i->timed_out;
|
||||
}
|
||||
|
||||
int num_blocks = pp->blocks_in_piece;
|
||||
|
||||
printf("%5d: [", piece);
|
||||
for (int j = 0; j < num_blocks; ++j)
|
||||
{
|
||||
int index = pp ? peer_index(pp->blocks[j].peer(), peers) % 36 : -1;
|
||||
char chr = '+';
|
||||
if (index >= 0)
|
||||
chr = (index < 10)?'0' + index:'A' + index - 10;
|
||||
|
||||
char const* color = "";
|
||||
char const* multi_req = "";
|
||||
|
||||
if (pp->blocks[j].num_peers > 1)
|
||||
multi_req = esc("1");
|
||||
|
||||
if (pp->blocks[j].bytes_progress > 0
|
||||
&& pp->blocks[j].state == block_info::requested)
|
||||
{
|
||||
color = esc("33;7");
|
||||
chr = '0' + (pp->blocks[j].bytes_progress * 10 / pp->blocks[j].block_size);
|
||||
}
|
||||
else if (pp->blocks[j].state == block_info::finished) color = esc("32;7");
|
||||
else if (pp->blocks[j].state == block_info::writing) color = esc("36;7");
|
||||
else if (pp->blocks[j].state == block_info::requested) color = esc("0");
|
||||
else { color = esc("0"); chr = ' '; }
|
||||
|
||||
printf("%s%s%c%s", color, multi_req, chr, esc("0"));
|
||||
}
|
||||
printf("%s]", esc("0"));
|
||||
if (deadline != 0.f)
|
||||
printf(" deadline: %f last-req: %f timed_out: %d\n"
|
||||
, deadline, last_request, timed_out);
|
||||
else
|
||||
puts("\n");
|
||||
}
|
||||
#endif // TORRENT_DEBUG_STREAMING
|
||||
|
||||
struct busy_block_t
|
||||
{
|
||||
int peers;
|
||||
int index;
|
||||
bool operator<(busy_block_t rhs) const { return peers < rhs.peers; }
|
||||
};
|
||||
|
||||
void pick_busy_blocks(int piece, int blocks_in_piece
|
||||
, int timed_out
|
||||
, std::vector<piece_block>& interesting_blocks
|
||||
, piece_picker::downloading_piece const& pi)
|
||||
{
|
||||
// if there aren't any free blocks in the piece, and the piece is
|
||||
// old enough, we may switch into busy mode for this piece. In this
|
||||
// case busy_blocks and busy_count are set to contain the eligible
|
||||
// busy blocks we may pick
|
||||
// first, figure out which blocks are eligible for picking
|
||||
// in "busy-mode"
|
||||
busy_block_t* busy_blocks
|
||||
= TORRENT_ALLOCA(busy_block_t, blocks_in_piece);
|
||||
int busy_count = 0;
|
||||
|
||||
// pick busy blocks from the piece
|
||||
for (int k = 0; k < blocks_in_piece; ++k)
|
||||
{
|
||||
// only consider blocks that have been requested
|
||||
// and we're still waiting for them
|
||||
if (pi.info[k].state != piece_picker::block_info::state_requested)
|
||||
continue;
|
||||
|
||||
piece_block b(piece, k);
|
||||
|
||||
// only allow a single additional request per block, in order
|
||||
// to spread it out evenly across all stalled blocks
|
||||
if (pi.info[k].num_peers > timed_out)
|
||||
continue;
|
||||
|
||||
busy_blocks[busy_count].peers = pi.info[k].num_peers;
|
||||
busy_blocks[busy_count].index = k;
|
||||
++busy_count;
|
||||
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
printf(" [%d (%d)]", b.block_index, pi.info[k].num_peers);
|
||||
#endif
|
||||
}
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
// then sort blocks by the number of peers with requests
|
||||
// to the blocks (request the blocks with the fewest peers
|
||||
// first)
|
||||
std::sort(busy_blocks, busy_blocks + busy_count);
|
||||
|
||||
// then insert them into the interesting_blocks vector
|
||||
for (int k = 0; k < busy_count; ++k)
|
||||
{
|
||||
interesting_blocks.push_back(
|
||||
piece_block(piece, busy_blocks[k].index));
|
||||
}
|
||||
}
|
||||
|
||||
void pick_time_critical_block(std::vector<peer_connection*>& peers
|
||||
, std::vector<peer_connection*>& ignore_peers
|
||||
, std::set<peer_connection*>& peers_with_requests
|
||||
, piece_picker::downloading_piece const& pi
|
||||
, time_critical_piece* i
|
||||
, piece_picker* picker
|
||||
, int blocks_in_piece
|
||||
, int timed_out)
|
||||
{
|
||||
std::vector<piece_block> interesting_blocks;
|
||||
std::vector<piece_block> backup1;
|
||||
std::vector<piece_block> backup2;
|
||||
std::vector<int> ignore;
|
||||
|
||||
ptime now = time_now();
|
||||
|
||||
// loop until every block has been requested from this piece (i->piece)
|
||||
do
|
||||
{
|
||||
// if this peer's download time exceeds 2 seconds, we're done.
|
||||
// We don't want to build unreasonably long request queues
|
||||
if (!peers.empty() && peers[0]->download_queue_time() > milliseconds(2000))
|
||||
break;
|
||||
|
||||
// pick the peer with the lowest download_queue_time that has i->piece
|
||||
std::vector<peer_connection*>::iterator p = std::find_if(peers.begin(), peers.end()
|
||||
, boost::bind(&peer_connection::has_piece, _1, i->piece));
|
||||
|
||||
// obviously we'll have to skip it if we don't have a peer that has
|
||||
// this piece
|
||||
if (p == peers.end())
|
||||
{
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
printf("out of peers, done\n");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
peer_connection& c = **p;
|
||||
|
||||
interesting_blocks.clear();
|
||||
backup1.clear();
|
||||
backup2.clear();
|
||||
|
||||
// specifically request blocks with no affinity towards fast or slow
|
||||
// pieces. If we would, the picked block might end up in one of
|
||||
// the backup lists
|
||||
picker->add_blocks(i->piece, c.get_bitfield(), interesting_blocks
|
||||
, backup1, backup2, blocks_in_piece, 0, c.peer_info_struct()
|
||||
, ignore, piece_picker::none, 0);
|
||||
|
||||
interesting_blocks.insert(interesting_blocks.end()
|
||||
, backup1.begin(), backup1.end());
|
||||
interesting_blocks.insert(interesting_blocks.end()
|
||||
, backup2.begin(), backup2.end());
|
||||
|
||||
bool busy_mode = false;
|
||||
|
||||
if (interesting_blocks.empty())
|
||||
{
|
||||
busy_mode = true;
|
||||
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
printf("interesting_blocks.empty()\n");
|
||||
#endif
|
||||
|
||||
// there aren't any free blocks to pick, and the piece isn't
|
||||
// old enough to pick busy blocks yet. break to continue to
|
||||
// the next piece.
|
||||
if (timed_out == 0)
|
||||
{
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
printf("not timed out, moving on to next piece\n");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
printf("pick busy blocks\n");
|
||||
#endif
|
||||
|
||||
pick_busy_blocks(i->piece, blocks_in_piece, timed_out
|
||||
, interesting_blocks, pi);
|
||||
}
|
||||
|
||||
// we can't pick anything from this piece, we're done with it.
|
||||
// move on to the next one
|
||||
if (interesting_blocks.empty()) break;
|
||||
|
||||
piece_block b = interesting_blocks.front();
|
||||
|
||||
if (busy_mode)
|
||||
{
|
||||
// in busy mode we need to make sure we don't do silly
|
||||
// things like requesting the same block twice from the
|
||||
// same peer
|
||||
std::vector<pending_block> const& dq = c.download_queue();
|
||||
|
||||
bool already_requested = std::find_if(dq.begin(), dq.end()
|
||||
, has_block(b)) != dq.end();
|
||||
if (already_requested)
|
||||
{
|
||||
// if the piece is stalled, we may end up picking a block
|
||||
// that we've already requested from this peer. If so, we should
|
||||
// simply disregard this peer from this piece, since this peer
|
||||
// is likely to be causing the stall. We should request it
|
||||
// from the next peer in the list
|
||||
// the peer will be put back in the set for the next piece
|
||||
ignore_peers.push_back(*p);
|
||||
peers.erase(p);
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
printf("piece already requested by peer, try next peer\n");
|
||||
#endif
|
||||
// try next peer
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<pending_block> const& rq = c.request_queue();
|
||||
|
||||
bool already_in_queue = std::find_if(rq.begin(), rq.end()
|
||||
, has_block(b)) != rq.end();
|
||||
|
||||
if (already_in_queue)
|
||||
{
|
||||
if (!c.make_time_critical(b))
|
||||
{
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
printf("piece already time-critical and in queue for peer, trying next peer\n");
|
||||
#endif
|
||||
ignore_peers.push_back(*p);
|
||||
peers.erase(p);
|
||||
continue;
|
||||
}
|
||||
i->last_requested = now;
|
||||
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
printf("piece already in queue for peer, making time-critical\n");
|
||||
#endif
|
||||
|
||||
// we inserted a new block in the request queue, this
|
||||
// makes us actually send it later
|
||||
peers_with_requests.insert(peers_with_requests.begin(), &c);
|
||||
|
||||
// try next peer
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!c.add_request(b, peer_connection::req_time_critical
|
||||
| (busy_mode ? peer_connection::req_busy : 0)))
|
||||
{
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
printf("failed to request block [%d, %d]\n"
|
||||
, b.piece_index, b.block_index);
|
||||
#endif
|
||||
ignore_peers.push_back(*p);
|
||||
peers.erase(p);
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
printf("requested block [%d, %d]\n"
|
||||
, b.piece_index, b.block_index);
|
||||
#endif
|
||||
|
||||
if (!busy_mode) i->last_requested = now;
|
||||
|
||||
peers_with_requests.insert(peers_with_requests.begin(), &c);
|
||||
if (i->first_requested == min_time()) i->first_requested = now;
|
||||
|
||||
if (!c.can_request_time_critical())
|
||||
{
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
printf("peer cannot pick time critical pieces\n");
|
||||
#endif
|
||||
peers.erase(p);
|
||||
// try next peer
|
||||
continue;
|
||||
}
|
||||
|
||||
// resort p, since it will have a higher download_queue_time now
|
||||
while (p != peers.end()-1 && (*p)->download_queue_time()
|
||||
> (*(p+1))->download_queue_time())
|
||||
{
|
||||
std::iter_swap(p, p+1);
|
||||
++p;
|
||||
}
|
||||
} while (!interesting_blocks.empty());
|
||||
|
||||
}
|
||||
|
||||
void torrent::request_time_critical_pieces()
|
||||
{
|
||||
TORRENT_ASSERT(m_ses.is_network_thread());
|
||||
TORRENT_ASSERT(!upload_mode());
|
||||
|
||||
// build a list of peers and sort it by download_queue_time
|
||||
// we use this sorted list to determine which peer we should
|
||||
// request a block from. The higher up a peer is in the list,
|
||||
|
@ -8234,11 +8583,6 @@ namespace libtorrent
|
|||
|
||||
std::set<peer_connection*> peers_with_requests;
|
||||
|
||||
std::vector<piece_block> interesting_blocks;
|
||||
std::vector<piece_block> backup1;
|
||||
std::vector<piece_block> backup2;
|
||||
std::vector<int> ignore;
|
||||
|
||||
// peers that should be temporarily ignored for a specific piece
|
||||
// in order to give priority to other peers. They should be used for
|
||||
// subsequent pieces, so they are stored in this vector until the
|
||||
|
@ -8253,33 +8597,75 @@ namespace libtorrent
|
|||
for (std::deque<time_critical_piece>::iterator i = m_time_critical_pieces.begin()
|
||||
, end(m_time_critical_pieces.end()); i != end; ++i)
|
||||
{
|
||||
if (peers.empty()) break;
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
printf("considering %d\n", i->piece);
|
||||
#endif
|
||||
|
||||
// the +1000 is to compensate for the fact that we only call this function
|
||||
// once per second, so if we need to request it 500 ms from now, we should request
|
||||
// it right away
|
||||
if (peers.empty())
|
||||
{
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
printf("out of peers, done\n");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
// the +1000 is to compensate for the fact that we only call this
|
||||
// function once per second, so if we need to request it 500 ms from
|
||||
// now, we should request it right away
|
||||
if (i != m_time_critical_pieces.begin() && i->deadline > now
|
||||
+ milliseconds(m_average_piece_time + m_piece_time_deviation * 4 + 1000))
|
||||
{
|
||||
// don't request pieces whose deadline is too far in the future
|
||||
// this is one of the termination conditions. We don't want to
|
||||
// send requests for all pieces in the torrent right away
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
printf("reached deadline horizon [%f + %f * 4 + 1]\n"
|
||||
, m_average_piece_time / 1000.f
|
||||
, m_piece_time_deviation / 1000.f);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
piece_picker::downloading_piece pi;
|
||||
m_picker->piece_info(i->piece, pi);
|
||||
|
||||
bool timed_out = false;
|
||||
// the number of "times" this piece has timed out.
|
||||
int timed_out = 0;
|
||||
|
||||
int blocks_in_piece = m_picker->blocks_in_piece(i->piece);
|
||||
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
i->timed_out = timed_out;
|
||||
#endif
|
||||
int free_to_request = blocks_in_piece
|
||||
- pi.finished - pi.writing - pi.requested;
|
||||
|
||||
int free_to_request = m_picker->blocks_in_piece(i->piece) - pi.finished - pi.writing - pi.requested;
|
||||
if (free_to_request == 0)
|
||||
{
|
||||
if (i->last_requested == min_time())
|
||||
i->last_requested = now;
|
||||
|
||||
// if it's been more than half of the typical download time
|
||||
// of a piece since we requested the last block, allow
|
||||
// one more request per block
|
||||
if (m_average_piece_time > 0)
|
||||
timed_out = total_milliseconds(now - i->last_requested)
|
||||
/ (std::max)(int(m_average_piece_time + m_piece_time_deviation / 2), 1);
|
||||
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
i->timed_out = timed_out;
|
||||
#endif
|
||||
// every block in this piece is already requested
|
||||
// there's no need to consider this piece, unless it
|
||||
// appears to be stalled.
|
||||
if (pi.requested == 0 || i->last_requested + milliseconds(m_average_piece_time) > now)
|
||||
if (pi.requested == 0 || timed_out == 0)
|
||||
{
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
printf("skipping %d (full) [req: %d timed_out: %d ]\n"
|
||||
, i->piece, pi.requested
|
||||
, timed_out);
|
||||
#endif
|
||||
|
||||
// if requested is 0, it meants all blocks have been received, and
|
||||
// we're just waiting for it to flush them to disk.
|
||||
// if last_requested is recent enough, we should give it some
|
||||
|
@ -8288,103 +8674,19 @@ namespace libtorrent
|
|||
continue;
|
||||
}
|
||||
|
||||
// it's been too long since we requested the last block from this piece. Allow re-requesting
|
||||
// blocks from this piece
|
||||
timed_out = true;
|
||||
// it's been too long since we requested the last block from
|
||||
// this piece. Allow re-requesting blocks from this piece
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
printf("timed out [average-piece-time: %d ms ]\n"
|
||||
, m_average_piece_time);
|
||||
#endif
|
||||
}
|
||||
|
||||
// loop until every block has been requested from this piece (i->piece)
|
||||
do
|
||||
{
|
||||
// pick the peer with the lowest download_queue_time that has i->piece
|
||||
std::vector<peer_connection*>::iterator p = std::find_if(peers.begin(), peers.end()
|
||||
, boost::bind(&peer_connection::has_piece, _1, i->piece));
|
||||
|
||||
// obviously we'll have to skip it if we don't have a peer that has this piece
|
||||
if (p == peers.end()) break;
|
||||
peer_connection& c = **p;
|
||||
|
||||
interesting_blocks.clear();
|
||||
backup1.clear();
|
||||
backup2.clear();
|
||||
// specifically request blocks with no affinity towards fast or slow
|
||||
// pieces. If we would, the picked block might end up in one of
|
||||
// the backup lists
|
||||
m_picker->add_blocks(i->piece, c.get_bitfield(), interesting_blocks
|
||||
, backup1, backup2, 1, 0, c.peer_info_struct()
|
||||
, ignore, piece_picker::none, 0);
|
||||
|
||||
std::vector<pending_block> const& rq = c.request_queue();
|
||||
std::vector<pending_block> const& dq = c.download_queue();
|
||||
|
||||
bool added_request = false;
|
||||
bool busy_blocks = false;
|
||||
|
||||
if (timed_out && interesting_blocks.empty())
|
||||
{
|
||||
// if the piece has timed out, allow requesting back-up blocks
|
||||
interesting_blocks.swap(backup1.empty() ? backup2 : backup1);
|
||||
busy_blocks = true;
|
||||
}
|
||||
|
||||
if (!interesting_blocks.empty())
|
||||
{
|
||||
bool already_requested = std::find_if(dq.begin(), dq.end()
|
||||
, has_block(interesting_blocks.front())) != dq.end();
|
||||
if (already_requested)
|
||||
{
|
||||
// if the piece is stalled, we may end up picking a block
|
||||
// that we've already requested from this peer. If so, we should
|
||||
// simply disregard this peer from this piece, since this peer
|
||||
// is likely to be causing the stall. We should request it
|
||||
// from the next peer in the list
|
||||
// the peer will be put back in the set for the next piece
|
||||
ignore_peers.push_back(*p);
|
||||
peers.erase(p);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool already_in_queue = std::find_if(rq.begin(), rq.end()
|
||||
, has_block(interesting_blocks.front())) != rq.end();
|
||||
|
||||
if (already_in_queue)
|
||||
{
|
||||
c.make_time_critical(interesting_blocks.front());
|
||||
added_request = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!c.add_request(interesting_blocks.front(), peer_connection::req_time_critical
|
||||
| (busy_blocks ? peer_connection::req_busy : 0)))
|
||||
{
|
||||
peers.erase(p);
|
||||
continue;
|
||||
}
|
||||
added_request = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (added_request)
|
||||
{
|
||||
peers_with_requests.insert(peers_with_requests.begin(), &c);
|
||||
if (i->first_requested == min_time()) i->first_requested = now;
|
||||
|
||||
if (!c.can_request_time_critical())
|
||||
{
|
||||
peers.erase(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
// resort p, since it will have a higher download_queue_time now
|
||||
while (p != peers.end()-1 && (*p)->download_queue_time() > (*(p+1))->download_queue_time())
|
||||
{
|
||||
std::iter_swap(p, p+1);
|
||||
++p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} while (!interesting_blocks.empty());
|
||||
// pick all blocks
|
||||
pick_time_critical_block(peers, ignore_peers
|
||||
, peers_with_requests
|
||||
, pi, &*i, m_picker.get()
|
||||
, blocks_in_piece, timed_out);
|
||||
|
||||
peers.insert(peers.begin(), ignore_peers.begin(), ignore_peers.end());
|
||||
ignore_peers.clear();
|
||||
|
@ -8396,6 +8698,27 @@ namespace libtorrent
|
|||
{
|
||||
(*i)->send_block_requests();
|
||||
}
|
||||
|
||||
#ifdef TORRENT_DEBUG_STREAMING
|
||||
std::vector<partial_piece_info> queue;
|
||||
get_download_queue(&queue);
|
||||
|
||||
std::vector<peer_info> peer_list;
|
||||
get_peer_info(peer_list);
|
||||
|
||||
std::sort(queue.begin(), queue.end(), boost::bind(&partial_piece_info::piece_index, _1)
|
||||
< boost::bind(&partial_piece_info::piece_index, _2));
|
||||
|
||||
puts("\033[2J\033[0;0H");
|
||||
printf("average piece download time: %.2f s (+/- %.2f s)\n"
|
||||
, m_average_piece_time / 1000.f
|
||||
, m_piece_time_deviation / 1000.f);
|
||||
for (std::vector<partial_piece_info>::iterator i = queue.begin()
|
||||
, end(queue.end()); i != end; ++i)
|
||||
{
|
||||
print_piece(&*i, peer_list, m_time_critical_pieces);
|
||||
}
|
||||
#endif // TORRENT_DEBUG_STREAMING
|
||||
}
|
||||
|
||||
std::set<std::string> torrent::web_seeds(web_seed_entry::type_t type) const
|
||||
|
|
Loading…
Reference in New Issue