diff --git a/docs/manual.rst b/docs/manual.rst index 4795a2bc3..470dafee6 100644 --- a/docs/manual.rst +++ b/docs/manual.rst @@ -476,6 +476,9 @@ number of uploads is at least one per torrent. ``max_connections()`` returns the current setting. +The number of unchoke slots may be ignored. In order to make this setting +take effect, disable ``session_settings::auto_upload_slots_rate_based``. + num_uploads() num_connections() ------------------------------- @@ -3306,6 +3309,7 @@ that will be sent to the tracker. The user-agent is a good way to identify your bool upnp_ignore_nonrouters; int send_buffer_watermark; bool auto_upload_slots; + bool auto_upload_slots_rate_based; bool use_parole_mode; int cache_size; int cache_expiry; @@ -3530,6 +3534,11 @@ of time, on upload slot is closed. The number of upload slots will never be less than what has been set by ``session::set_max_uploads()``. To query the current number of upload slots, see ``session_status::allowed_upload_slots``. +When ``auto_upload_slots_rate_based`` is set, and ``auto_upload_slots`` is set, +the max upload slots setting is ignored and decided completely automatically. +This algorithm is designed to prevent the peer from spreading its upload +capacity too thin. + ``use_parole_mode`` specifies if parole mode should be used. Parole mode means that peers that participate in pieces that fail the hash check are put in a mode where they are only allowed to download whole pieces. If the whole piece a peer diff --git a/examples/client_test.cpp b/examples/client_test.cpp index d79205142..e190755ae 100644 --- a/examples/client_test.cpp +++ b/examples/client_test.cpp @@ -713,6 +713,7 @@ int main(int argc, char* argv[]) proxy_settings ps; settings.user_agent = "client_test/" LIBTORRENT_VERSION; + settings.auto_upload_slots_rate_based = true; settings.announce_to_all_trackers = true; std::deque events; diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp index d5397fce2..b4c36e115 100644 --- a/include/libtorrent/aux_/session_impl.hpp +++ b/include/libtorrent/aux_/session_impl.hpp @@ -90,6 +90,7 @@ namespace libtorrent class natpmp; class lsd; class fingerprint; + class torrent; namespace dht { @@ -520,6 +521,10 @@ namespace libtorrent ptime m_last_tick; + // the last time we went through the peers + // to decide which ones to choke/unchoke + ptime m_last_choke; + // when outgoing_ports is configured, this is the // port we'll bind the next outgoing socket to int m_next_port; diff --git a/include/libtorrent/peer_connection.hpp b/include/libtorrent/peer_connection.hpp index a64cc0ef5..1304d7e0f 100644 --- a/include/libtorrent/peer_connection.hpp +++ b/include/libtorrent/peer_connection.hpp @@ -361,6 +361,7 @@ namespace libtorrent // for which one is more eligible for an unchoke. // returns true if this is more eligible bool unchoke_compare(boost::intrusive_ptr const& p) const; + bool upload_rate_compare(peer_connection const* p) const; // resets the byte counters that are used to measure // the number of bytes transferred within unchoke cycles @@ -505,6 +506,12 @@ namespace libtorrent // enum from peer_info::bw_state char m_channel_state[2]; + size_type uploaded_since_unchoke() const + { return m_statistics.total_payload_upload() - m_uploaded_at_last_unchoke; } + + size_type downloaded_since_unchoke() const + { return m_statistics.total_payload_download() - m_downloaded_at_last_unchoke; } + protected: virtual void get_specific_peer_info(peer_info& p) const = 0; diff --git a/include/libtorrent/session_settings.hpp b/include/libtorrent/session_settings.hpp index f27d14483..fa8e60a79 100644 --- a/include/libtorrent/session_settings.hpp +++ b/include/libtorrent/session_settings.hpp @@ -121,6 +121,7 @@ namespace libtorrent , upnp_ignore_nonrouters(false) , send_buffer_watermark(80 * 1024) , auto_upload_slots(true) + , auto_upload_slots_rate_based(false) , use_parole_mode(true) , cache_size(512) , cache_expiry(60) @@ -363,6 +364,11 @@ namespace libtorrent // the manual settings, through max_uploads. bool auto_upload_slots; + // this only affects the auto upload slots mechanism. + // if auto_upload_slots is false, this field is not + // considered. + bool auto_upload_slots_rate_based; + // if set to true, peers that participate in a failing // piece is put in parole mode. i.e. They will only // download whole pieces until they either fail or pass. diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp index ab3d565eb..f1175dd82 100644 --- a/src/peer_connection.cpp +++ b/src/peer_connection.cpp @@ -336,6 +336,17 @@ namespace libtorrent return m_last_unchoke < rhs.m_last_unchoke; } + bool peer_connection::upload_rate_compare(peer_connection const* p) const + { + size_type c1; + size_type c2; + + c1 = m_statistics.total_payload_upload() - m_uploaded_at_last_unchoke; + c2 = p->m_statistics.total_payload_upload() - p->m_uploaded_at_last_unchoke; + + return c1 > c2; + } + void peer_connection::reset_choke_counters() { m_downloaded_at_last_unchoke = m_statistics.total_payload_download(); diff --git a/src/session_impl.cpp b/src/session_impl.cpp index fbc1025de..be8062b2b 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -180,6 +180,7 @@ namespace aux { , m_auto_scrape_time_scaler(180) , m_incoming_connection(false) , m_last_tick(time_now()) + , m_last_choke(m_last_tick) #ifndef TORRENT_DISABLE_DHT , m_dht_same_port(true) , m_external_udp_port(0) @@ -1644,6 +1645,12 @@ namespace aux { { INVARIANT_CHECK; + ptime now = time_now(); + time_duration unchoke_interval = now - m_last_choke; + m_last_choke = now; + + // build list of all peers that are + // unchoke:able. std::vector peers; for (connection_map::iterator i = m_connections.begin(); i != m_connections.end();) @@ -1652,31 +1659,56 @@ namespace aux { TORRENT_ASSERT(p); ++i; torrent* t = p->associated_torrent().lock().get(); - if (!p->peer_info_struct() - || t == 0 - || !p->is_peer_interested() + policy::peer* pi = p->peer_info_struct(); + if (p->ignore_unchoke_slots() || t == 0 || pi == 0) continue; + + if (!p->is_peer_interested() || p->is_disconnecting() || p->is_connecting() - || p->ignore_unchoke_slots() || (p->share_diff() < -free_upload_amount && !t->is_seed())) { - if (!p->is_choked() && t && !p->ignore_unchoke_slots()) + // this peer is not unchokable. So, if it's unchoked + // already, make sure to choke it. + if (p->is_choked()) continue; + if (pi && pi->optimistically_unchoked) { - policy::peer* pi = p->peer_info_struct(); - if (pi && pi->optimistically_unchoked) - { - pi->optimistically_unchoked = false; - // force a new optimistic unchoke - m_optimistic_unchoke_time_scaler = 0; - } - t->choke_peer(*p); + pi->optimistically_unchoked = false; + // force a new optimistic unchoke + m_optimistic_unchoke_time_scaler = 0; } - continue; + t->choke_peer(*p); } peers.push_back(p.get()); } + // if the client is configured to use fully automatic + // unchoke slots, ignore m_max_uploads + if (m_settings.auto_upload_slots_rate_based + && m_settings.auto_upload_slots) + { + m_allowed_upload_slots = 0; + std::sort(peers.begin(), peers.end() + , bind(&peer_connection::upload_rate_compare, _1, _2)); + + // TODO: make configurable + int rate_threshold = 1024; + + for (std::vector::const_iterator i = peers.begin() + , end(peers.end()); i != end; ++i) + { + peer_connection const& p = **i; + int rate = p.uploaded_since_unchoke() + * 1000 / total_milliseconds(unchoke_interval); + if (rate > rate_threshold) ++m_allowed_upload_slots; + + // TODO: make configurable + rate_threshold += 1024; + } + // allow one optimistic unchoke + ++m_allowed_upload_slots; + } + // sorts the peers that are eligible for unchoke by download rate and secondary // by total upload. The reason for this is, if all torrents are being seeded, // the download rate will be 0, and the peers we have sent the least to should @@ -1689,7 +1721,9 @@ namespace aux { // auto unchoke int upload_limit = m_bandwidth_manager[peer_connection::upload_channel]->throttle(); - if (m_settings.auto_upload_slots && upload_limit != bandwidth_limit::inf) + if (!m_settings.auto_upload_slots_rate_based + && m_settings.auto_upload_slots + && upload_limit != bandwidth_limit::inf) { // if our current upload rate is less than 90% of our // limit AND most torrents are not "congested", i.e. @@ -1720,11 +1754,12 @@ namespace aux { { peer_connection* p = *i; TORRENT_ASSERT(p); - if (p->ignore_unchoke_slots()) continue; + TORRENT_ASSERT(!p->ignore_unchoke_slots()); torrent* t = p->associated_torrent().lock().get(); TORRENT_ASSERT(t); if (unchoke_set_size > 0) { + // yes, this peer should be unchoked if (p->is_choked()) { if (!t->unchoke_peer(*p)) @@ -1746,6 +1781,7 @@ namespace aux { } else { + // no, this peer should be shoked TORRENT_ASSERT(p->peer_info_struct()); if (!p->is_choked() && !p->peer_info_struct()->optimistically_unchoked) t->choke_peer(*p); @@ -2749,7 +2785,8 @@ namespace aux { std::set unique_peers; TORRENT_ASSERT(m_max_connections > 0); TORRENT_ASSERT(m_max_uploads > 0); - TORRENT_ASSERT(m_allowed_upload_slots >= m_max_uploads); + if (!m_settings.auto_upload_slots_rate_based || !m_settings.auto_upload_slots) + TORRENT_ASSERT(m_allowed_upload_slots >= m_max_uploads); int unchokes = 0; int num_optimistic = 0; for (connection_map::const_iterator i = m_connections.begin();