From fb47469834f098ad6112441e89074c5e3e90f403 Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Tue, 9 Feb 2010 03:04:41 +0000 Subject: [PATCH] experimental support for the BitTyrant choking algorithm --- ChangeLog | 1 + bindings/python/src/session_settings.cpp | 10 ++ docs/manual.rst | 99 ++++++++++++++---- examples/client_test.cpp | 10 +- include/libtorrent/alert_types.hpp | 4 +- include/libtorrent/aux_/session_impl.hpp | 3 + include/libtorrent/peer_connection.hpp | 26 ++++- include/libtorrent/peer_info.hpp | 2 + include/libtorrent/session_settings.hpp | 49 +++++++-- src/alert.cpp | 3 +- src/bandwidth_manager.cpp | 2 +- src/bandwidth_queue_entry.cpp | 3 +- src/peer_connection.cpp | 124 +++++++++++++++++++++-- src/session.cpp | 2 +- src/session_impl.cpp | 119 +++++++++++++++++++--- test/test_auto_unchoke.cpp | 11 +- 16 files changed, 396 insertions(+), 72 deletions(-) diff --git a/ChangeLog b/ChangeLog index a9135970a..2984ddac6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -13,6 +13,7 @@ * optimized disk cache to work with large caches * support variable number of optimistic unchoke slots and to dynamically adjust based on the total number of unchoke slots + * support for BitTyrant choker algorithm 0.15 release diff --git a/bindings/python/src/session_settings.cpp b/bindings/python/src/session_settings.cpp index 102d37786..c5d7bf983 100644 --- a/bindings/python/src/session_settings.cpp +++ b/bindings/python/src/session_settings.cpp @@ -49,8 +49,11 @@ void bind_session_settings() .def_readwrite("free_torrent_hashes", &session_settings::free_torrent_hashes) .def_readwrite("upnp_ignore_nonrouters", &session_settings::upnp_ignore_nonrouters) .def_readwrite("send_buffer_watermark", &session_settings::send_buffer_watermark) +#ifndef TORRENT_NO_DEPRECATE .def_readwrite("auto_upload_slots", &session_settings::auto_upload_slots) .def_readwrite("auto_upload_slots_rate_based", &session_settings::auto_upload_slots_rate_based) +#endif + .def_readwrite("choking_algorithm", &session_settings::choking_algorithm) .def_readwrite("use_parole_mode", &session_settings::use_parole_mode) .def_readwrite("cache_size", &session_settings::cache_size) .def_readwrite("cache_buffer_chunk_size", &session_settings::cache_buffer_chunk_size) @@ -126,6 +129,13 @@ void bind_session_settings() .value("largest_contiguous", session_settings::largest_contiguous) ; + enum_("choking_algorithm_t") + .value("fixed_slots_choker", session_settings::fixed_slots_choker) + .value("auto_expand_choker", session_settings::auto_expand_choker) + .value("rate_based_choker", session_settings::rate_based_choker) + .value("bittyrant_choker", session_settings::bittyrant_choker) + ; + enum_("io_buffer_mode_t") .value("enable_os_cache", session_settings::enable_os_cache) .value("disable_os_cache_for_aligned_files", session_settings::disable_os_cache_for_aligned_files) diff --git a/docs/manual.rst b/docs/manual.rst index 933b4fe2d..a4eecdae7 100644 --- a/docs/manual.rst +++ b/docs/manual.rst @@ -547,26 +547,37 @@ quite unthrottled. A rate limit of 0 means infinite. -set_max_uploads() set_max_connections() max_uploads() max_connections() ------------------------------------------------------------------------ +set_max_uploads() max_uploads() +------------------------------- :: void set_max_uploads(int limit); - void set_max_connections(int limit); int max_uploads() const; + +``set_max_uploads`` sets a global limit on the number of unchoked peers (uploads). +The number of uploads is at least one per torrent. + +``max_uploads()`` returns the current settings. + +The number of unchoke slots may be ignored depending on what +``session_settings::choking_algorithm`` is set to. + + +set_max_connections() max_connections() +--------------------------------------- + + :: + + void set_max_connections(int limit); int max_connections() const; -These functions will set a global limit on the number of unchoked peers (uploads) -and the number of connections opened. The number of connections is set to a hard -minimum of at least two connections per torrent, so if you set a too low -connections limit, and open too many torrents, the limit will not be met. The -number of uploads is at least one per torrent. +``set_max_connections`` sets a global limit on the number of connections +opened. The number of connections is set to a hard minimum of at least two per +torrent, so if you set a too low connections limit, and open too many torrents, +the limit will not be met. -``max_uploads()`` and ``max_connections()`` returns the current settings. - -The number of unchoke slots may be ignored. In order to make this setting -take effect, disable ``session_settings::auto_upload_slots_rate_based``. +``max_connections()`` returns the current settings. num_uploads() num_connections() @@ -2640,6 +2651,9 @@ consume, even if there's is more quota. Other peers will still be weighed in whe bandwidth is being distributed. With other words, bandwidth is not distributed strictly in order of priority, but the priority is used as a weight. +Torrents with higher priority are also more likely to have its peers unchoked, to +distribute more upload capacity to them. + use_interface() --------------- @@ -2663,25 +2677,34 @@ info_hash() ``info_hash()`` returns the info-hash for the torrent. -set_max_uploads() max_uploads() set_max_connections() max_connections() ------------------------------------------------------------------------ +set_max_uploads() max_uploads() +------------------------------- :: void set_max_uploads(int max_uploads) const; int max_uploads() const; - void set_max_connections(int max_connections) const; - int max_connections() const; ``set_max_uploads()`` sets the maximum number of peers that's unchoked at the same time on this torrent. If you set this to -1, there will be no limit. +``max_uploads()`` returns the current settings. + + +set_max_connections() max_connections() +--------------------------------------- + + :: + + void set_max_connections(int max_connections) const; + int max_connections() const; + ``set_max_connections()`` sets the maximum number of connection this torrent will open. If all connections are used up, incoming connections may be refused or poor connections may be closed. This must be at least 2. The default is unlimited number of connections. If -1 is given to the function, it means unlimited. -``max_uploads()`` and ``max_connections()`` returns the current settings. +``max_connections()`` returns the current settings. save_resume_data() @@ -3648,8 +3671,22 @@ session_settings bool free_torrent_hashes; bool upnp_ignore_nonrouters; int send_buffer_watermark; + + #ifndef TORRENT_NO_DEPRECATE bool auto_upload_slots; bool auto_upload_slots_rate_based; + #endif + + enum choking_algorithm_t + { + fixed_slots_choker, + auto_expand_choker, + rate_based_choker, + bittyrant_choker + }; + + int choking_algorithm; + bool use_parole_mode; int cache_size; int cache_buffer_chunk_size; @@ -3928,6 +3965,34 @@ the max upload slots setting is used as a minimum number of unchoked slots. This algorithm is designed to prevent the peer from spreading its upload capacity too thin, but still open more slots in order to utilize the full capacity. +``choking_algorithm`` specifies which algorithm to use to determine which peers +to unchoke. This setting replaces the deprecated settings ``auto_upload_slots`` +and ``auto_upload_slots_rate_based``. + +The options for choking algorithms are: + +* ``fixed_slots_choker`` is the traditional choker with a fixed number of unchoke + slots (as specified by ``session::set_max_uploads()``). + +* ``auto_expand_choker`` opens at least the number of slots as specified by + ``session::set_max_uploads()`` but opens up more slots if the upload capacity + is not saturated. This unchoker will work just like the ``fixed_slot_choker`` + if there's no global upload rate limit set. + +* ``rate_based_choker`` opens up unchoke slots based on the upload rate + achieved to peers. The more slots that are opened, the marginal upload + rate required to open up another slot increases. + +* ``bittyrant_choker`` attempts to optimize download rate by finding the + reciprocation rate of each peer individually and prefers peers that gives + the highest *return on investment*. It still allocates all upload capacity, + but shuffles it around to the best peers first. For this choker to be + efficient, you need to set a global upload rate limit + (``session::set_upload_rate_limit()``). For more information about this + choker, see the paper_. + +.. _paper: http://bittyrant.cs.washington.edu/#papers + ``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 a51ceed6b..19f848539 100644 --- a/examples/client_test.cpp +++ b/examples/client_test.cpp @@ -342,7 +342,7 @@ void print_peer_info(std::string& out, std::vector const& #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES out += "country "; #endif - if (print_peer_rate) out += "peer-rate "; + if (print_peer_rate) out += "peer-rate est.rec.rate "; out += "client \n"; char str[500]; @@ -462,7 +462,11 @@ void print_peer_info(std::string& out, std::vector const& #endif if (print_peer_rate) { - snprintf(str, sizeof(str), " %s", add_suffix(i->remote_dl_rate, "/s").c_str()); + bool unchoked = (i->flags & peer_info::choked) == 0; + + snprintf(str, sizeof(str), " %s %s %s" + , add_suffix(i->remote_dl_rate, "/s").c_str() + , unchoked ? add_suffix(i->estimated_reciprocation_rate, "/s").c_str() : " "); out += str; } out += " "; @@ -771,7 +775,7 @@ int main(int argc, char* argv[]) session_settings settings; settings.user_agent = "client_test/" LIBTORRENT_VERSION; - settings.auto_upload_slots_rate_based = true; + settings.choking_algorithm = session_settings::auto_expand_choker; //settings.announce_to_all_trackers = true; settings.optimize_hashing_for_speed = false; settings.disk_cache_algorithm = session_settings::largest_contiguous; diff --git a/include/libtorrent/alert_types.hpp b/include/libtorrent/alert_types.hpp index 5b4e08ff4..e1d291cc7 100644 --- a/include/libtorrent/alert_types.hpp +++ b/include/libtorrent/alert_types.hpp @@ -188,8 +188,8 @@ namespace libtorrent upload_limit_too_low, download_limit_too_low, send_buffer_watermark_too_low, - too_many_optimistic_unchoke_slots - + too_many_optimistic_unchoke_slots, + bittyrant_with_no_uplimit diff --git a/include/libtorrent/aux_/session_impl.hpp b/include/libtorrent/aux_/session_impl.hpp index 9a44bbde2..48d9fcc73 100644 --- a/include/libtorrent/aux_/session_impl.hpp +++ b/include/libtorrent/aux_/session_impl.hpp @@ -643,6 +643,9 @@ namespace libtorrent // statistics gathered from all torrents. stat m_stat; + int m_peak_up_rate; + int m_peak_down_rate; + // is false by default and set to true when // the first incoming connection is established // this is used to know if the client is behind diff --git a/include/libtorrent/peer_connection.hpp b/include/libtorrent/peer_connection.hpp index 7bed85806..5a0c5fdaf 100644 --- a/include/libtorrent/peer_connection.hpp +++ b/include/libtorrent/peer_connection.hpp @@ -385,6 +385,8 @@ namespace libtorrent int desired_queue_size() const { return m_desired_queue_size; } + bool peer_connection::bittyrant_unchoke_compare( + boost::intrusive_ptr const& p) const; // compares this connection against the given connection // for which one is more eligible for an unchoke. // returns true if this is more eligible @@ -399,6 +401,10 @@ namespace libtorrent // interested in the other), disconnect it void disconnect_if_redundant(); + void increase_est_reciprocation_rate(); + void decrease_est_reciprocation_rate(); + int est_reciprocation_rate() const { return m_est_reciprocation_rate; } + #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING boost::shared_ptr m_logger; #endif @@ -692,6 +698,10 @@ namespace libtorrent // the time when we unchoked this peer ptime m_last_unchoke; + // if we're unchoked by this peer, this + // was the time + ptime m_last_unchoked; + // timeouts ptime m_last_receive; ptime m_last_sent; @@ -702,11 +712,6 @@ namespace libtorrent // download queue. Used for request timeout ptime m_requested; - // if the timeout is extended for the outstanding - // requests, this is the number of seconds it was - // extended. - int m_timeout_extend; - // a timestamp when the remote download rate // was last updated ptime m_remote_dl_update; @@ -804,6 +809,11 @@ namespace libtorrent // (-1, -1) if we're not receiving one piece_block m_receiving_block; + // if the timeout is extended for the outstanding + // requests, this is the number of seconds it was + // extended. + int m_timeout_extend; + // the number of bytes that the other // end has to send us in order to respond // to all outstanding piece requests we @@ -905,6 +915,12 @@ namespace libtorrent int m_download_rate_peak; int m_upload_rate_peak; + // when using the BitTyrant choker, this is our + // estimated reciprocation rate. i.e. the rate + // we need to send to this peer for it to unchoke + // us + int m_est_reciprocation_rate; + // estimated round trip time to this peer // based on the time from when async_connect // was called to when on_connection_complete diff --git a/include/libtorrent/peer_info.hpp b/include/libtorrent/peer_info.hpp index 40fd77ccc..de59c9d0e 100644 --- a/include/libtorrent/peer_info.hpp +++ b/include/libtorrent/peer_info.hpp @@ -214,6 +214,8 @@ namespace libtorrent // the peers progress float progress; // [0, 1] int progress_ppm; // [0, 1000000] + + int estimated_reciprocation_rate; }; struct TORRENT_EXPORT peer_list_entry diff --git a/include/libtorrent/session_settings.hpp b/include/libtorrent/session_settings.hpp index fc7d1584b..dc55ccdc7 100644 --- a/include/libtorrent/session_settings.hpp +++ b/include/libtorrent/session_settings.hpp @@ -123,8 +123,12 @@ namespace libtorrent , free_torrent_hashes(true) , upnp_ignore_nonrouters(false) , send_buffer_watermark(100 * 1024) +#ifndef TORRENT_NO_DEPRECATE + // deprecated in 0.16 , auto_upload_slots(true) , auto_upload_slots_rate_based(true) +#endif + , choking_algorithm(rate_based_choker) , use_parole_mode(true) , cache_size(1024) , cache_buffer_chunk_size(16) @@ -193,6 +197,9 @@ namespace libtorrent , default_cache_min_age(1) , num_optimistic_unchoke_slots(0) , no_atime_storage(true) + , default_est_reciprocation_rate(16000) + , increase_est_reciprocation_rate(20) + , decrease_est_reciprocation_rate(3) {} // this is the user agent that will be sent to the tracker @@ -397,20 +404,22 @@ namespace libtorrent // the upload rate is low, this is the upper limit. int send_buffer_watermark; - // if auto_upload_slots is true, and a global upload - // limit is set and the upload rate is less than 90% - // of the upload limit, on new slot is opened up. If - // the upload rate is >= upload limit for an extended - // period of time, one upload slot is closed. The - // upload slots are never automatically decreased below - // the manual settings, through max_uploads. +#ifndef TORRENT_NO_DEPRECATE + // deprecated in 0.16 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; +#endif + enum choking_algorithm_t + { + fixed_slots_choker, + auto_expand_choker, + rate_based_choker, + bittyrant_choker + }; + + int choking_algorithm; + // 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. @@ -725,6 +734,24 @@ namespace libtorrent // if set to true, files won't have their atime updated // on disk reads. This works on linux bool no_atime_storage; + + // === BitTyrant unchoker settings == + + // when using BitTyrant choker, this is the default + // assumed reciprocation rate. This is where each peer starts + int default_est_reciprocation_rate; + + // this is the increase of the estimated reciprocation rate + // in percent. We increase by this amount once every unchoke + // interval that we are choked by the other peer and we have + // unchoked them + int increase_est_reciprocation_rate; + + // each unchoke interval that we stay unchoked by the other + // peer, and we have unchoked this peer as well, we decrease + // our estimate of the reciprocation rate, since we might have + // over-estimated it + int decrease_est_reciprocation_rate; }; #ifndef TORRENT_DISABLE_DHT diff --git a/src/alert.cpp b/src/alert.cpp index 09816d190..13a95d2af 100644 --- a/src/alert.cpp +++ b/src/alert.cpp @@ -113,7 +113,8 @@ namespace libtorrent { "upload limit too low (download rate will suffer)", "download limit too low (upload rate will suffer)", "send buffer watermark too low (upload rate will suffer)", - "too many optimistic unchoke slots" + "too many optimistic unchoke slots", + "using bittyrant unchoker with no upload rate limit set" }; return torrent_alert::message() + ": performance warning: " diff --git a/src/bandwidth_manager.cpp b/src/bandwidth_manager.cpp index a23e50193..5881e1010 100644 --- a/src/bandwidth_manager.cpp +++ b/src/bandwidth_manager.cpp @@ -180,8 +180,8 @@ namespace libtorrent { bandwidth_channel* bwc = i->channel[j]; if (bwc->tmp == 0) channels.push_back(bwc); + TORRENT_ASSERT(INT_MAX - bwc->tmp > i->priority); bwc->tmp += i->priority; - TORRENT_ASSERT(i->priority > 0); } } diff --git a/src/bandwidth_queue_entry.cpp b/src/bandwidth_queue_entry.cpp index 9fd2d681a..d95b219d6 100644 --- a/src/bandwidth_queue_entry.cpp +++ b/src/bandwidth_queue_entry.cpp @@ -57,6 +57,8 @@ namespace libtorrent for (int j = 0; j < 5 && channel[j]; ++j) { if (channel[j]->throttle() == 0) continue; + TORRENT_ASSERT(channel[j]->distribute_quota + < (std::numeric_limits::max)() / priority); quota = (std::min)(int(boost::uint64_t(channel[j]->distribute_quota) * priority / channel[j]->tmp), quota); } @@ -65,7 +67,6 @@ namespace libtorrent channel[j]->use_quota(quota); TORRENT_ASSERT(assigned <= request_size); --ttl; - TORRENT_ASSERT(assigned <= request_size); return quota; } } diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp index ece050412..c21e868ef 100644 --- a/src/peer_connection.cpp +++ b/src/peer_connection.cpp @@ -85,10 +85,10 @@ namespace libtorrent , m_last_request(time_now()) , m_last_incoming_request(min_time()) , m_last_unchoke(time_now()) + , m_last_unchoked(time_now()) , m_last_receive(time_now()) , m_last_sent(time_now()) , m_requested(min_time()) - , m_timeout_extend(0) , m_remote_dl_update(time_now()) , m_connect(time_now()) , m_became_uninterested(time_now()) @@ -101,6 +101,7 @@ namespace libtorrent , m_remote(endp) , m_torrent(tor) , m_receiving_block(-1, -1) + , m_timeout_extend(0) , m_outstanding_bytes(0) , m_queued_time_critical(0) , m_num_pieces(0) @@ -153,6 +154,8 @@ namespace libtorrent , m_received_in_piece(0) #endif { + m_est_reciprocation_rate = m_ses.m_settings.default_est_reciprocation_rate; + #if TORRENT_USE_I2P if (peerinfo && peerinfo->is_i2p_addr) { @@ -216,10 +219,10 @@ namespace libtorrent , m_last_request(time_now()) , m_last_incoming_request(min_time()) , m_last_unchoke(time_now()) + , m_last_unchoked(time_now()) , m_last_receive(time_now()) , m_last_sent(time_now()) , m_requested(min_time()) - , m_timeout_extend(0) , m_remote_dl_update(time_now()) , m_connect(time_now()) , m_became_uninterested(time_now()) @@ -231,6 +234,7 @@ namespace libtorrent , m_socket(s) , m_remote(endp) , m_receiving_block(-1, -1) + , m_timeout_extend(0) , m_outstanding_bytes(0) , m_queued_time_critical(0) , m_num_pieces(0) @@ -283,6 +287,8 @@ namespace libtorrent , m_received_in_piece(0) #endif { + m_est_reciprocation_rate = m_ses.m_settings.default_est_reciprocation_rate; + #if TORRENT_USE_I2P if (peerinfo && peerinfo->is_i2p_addr) { @@ -340,6 +346,62 @@ namespace libtorrent } #endif + void peer_connection::increase_est_reciprocation_rate() + { + m_est_reciprocation_rate += m_est_reciprocation_rate + * m_ses.m_settings.increase_est_reciprocation_rate / 100; + } + + void peer_connection::decrease_est_reciprocation_rate() + { + m_est_reciprocation_rate -= m_est_reciprocation_rate + * m_ses.m_settings.decrease_est_reciprocation_rate / 100; + } + + bool peer_connection::bittyrant_unchoke_compare( + boost::intrusive_ptr const& p) const + { + TORRENT_ASSERT(p); + peer_connection const& rhs = *p; + + size_type d1, d2, u1, u2; + + // first compare how many bytes they've sent us + d1 = m_statistics.total_payload_download() - m_downloaded_at_last_unchoke; + d2 = rhs.m_statistics.total_payload_download() - rhs.m_downloaded_at_last_unchoke; + // divided by the number of bytes we've sent them + u1 = m_statistics.total_payload_upload() - m_uploaded_at_last_unchoke; + u2 = rhs.m_statistics.total_payload_upload() - rhs.m_uploaded_at_last_unchoke; + + boost::shared_ptr t1 = m_torrent.lock(); + TORRENT_ASSERT(t1); + boost::shared_ptr t2 = rhs.associated_torrent().lock(); + TORRENT_ASSERT(t2); + + // take torrent priority into account + d1 *= 1 + t1->priority(); + d2 *= 1 + t2->priority(); + + d1 = d1 * 1000 / (std::max)(size_type(1), u1); + d2 = d2 * 1000 / (std::max)(size_type(1), u2); + if (d1 > d2) return true; + if (d1 < d2) return false; + + // in order to not switch back and forth too often, + // unchoked peers must be at least one piece ahead + // of a choked peer to be sorted at a lower unchoke-priority + int pieces = m_ses.settings().seeding_piece_quota; + bool c1_done = is_choked() || u1 > (std::max)(t1->torrent_file().piece_length() * pieces, 256 * 1024); + bool c2_done = rhs.is_choked() || u2 > (std::max)(t2->torrent_file().piece_length() * pieces, 256 * 1024); + + if (!c1_done && c2_done) return true; + if (c1_done && !c2_done) return false; + + // if both peers are still in their send quota or not in their send quota + // prioritize the one that has waited the longest to be unchoked + return m_last_unchoke < rhs.m_last_unchoke; + } + bool peer_connection::unchoke_compare(boost::intrusive_ptr const& p) const { TORRENT_ASSERT(p); @@ -351,6 +413,16 @@ namespace libtorrent // first compare how many bytes they've sent us c1 = m_statistics.total_payload_download() - m_downloaded_at_last_unchoke; c2 = rhs.m_statistics.total_payload_download() - rhs.m_downloaded_at_last_unchoke; + + boost::shared_ptr t1 = m_torrent.lock(); + TORRENT_ASSERT(t1); + boost::shared_ptr t2 = rhs.associated_torrent().lock(); + TORRENT_ASSERT(t2); + + // take torrent priority into account + c1 *= 1 + t1->priority(); + c2 *= 1 + t2->priority(); + if (c1 > c2) return true; if (c1 < c2) return false; @@ -361,10 +433,6 @@ namespace libtorrent // in order to not switch back and forth too often, // unchoked peers must be at least one piece ahead // of a choked peer to be sorted at a lower unchoke-priority - boost::shared_ptr t1 = m_torrent.lock(); - TORRENT_ASSERT(t1); - boost::shared_ptr t2 = rhs.associated_torrent().lock(); - TORRENT_ASSERT(t2); int pieces = m_ses.settings().seeding_piece_quota; bool c1_done = is_choked() || c1 > (std::max)(t1->torrent_file().piece_length() * pieces, 256 * 1024); bool c2_done = rhs.is_choked() || c2 > (std::max)(t2->torrent_file().piece_length() * pieces, 256 * 1024); @@ -382,9 +450,18 @@ namespace libtorrent size_type c1; size_type c2; + boost::shared_ptr t1 = m_torrent.lock(); + TORRENT_ASSERT(t1); + boost::shared_ptr t2 = p->associated_torrent().lock(); + TORRENT_ASSERT(t2); + c1 = m_statistics.total_payload_upload() - m_uploaded_at_last_unchoke; c2 = p->m_statistics.total_payload_upload() - p->m_uploaded_at_last_unchoke; + // take torrent priority into account + c1 *= 1 + t1->priority(); + c2 *= 1 + t2->priority(); + return c1 > c2; } @@ -1280,6 +1357,7 @@ namespace libtorrent (*m_logger) << time_now_string() << " <== UNCHOKE\n"; #endif m_peer_choked = false; + m_last_unchoked = time_now(); if (is_disconnecting()) return; if (is_interesting()) @@ -3343,6 +3421,11 @@ namespace libtorrent #endif p.progress_ppm = p.pieces.count() * 1000000 / p.pieces.size(); } + + p.estimated_reciprocation_rate = m_est_reciprocation_rate; + int upload_capacity = m_ses.upload_rate_limit(); + if (upload_capacity == 0) + upload_capacity = (std::max)(20000, m_ses.m_peak_up_rate + 10000); } // allocates a disk buffer of size 'disk_buffer_size' and replaces the @@ -4014,10 +4097,31 @@ namespace libtorrent , bandwidth_channel* bwc4) { shared_ptr t = m_torrent.lock(); - int priority = 1 + is_interesting() * 2 + m_requests_in_buffer.size(); - - if (priority > 255) priority = 255; - priority += t->priority() << 8; + int priority; + if (m_ses.m_settings.choking_algorithm == session_settings::bittyrant_choker + && !t->is_upload_only()) + { + // when we use the bittyrant choker, the priority of a peer + // is decided based on the estimated reciprocation rate and + // the share it represents of the total upload rate capacity + // the torrent priority is taken into account when unchoking peers + int upload_capacity = m_ses.upload_rate_limit(); + if (upload_capacity == 0) + { + // we don't know at what rate we can upload. If we have a + // measurement of the peak, use that + 10kB/s, otherwise + // assume 20 kB/s + upload_capacity = (std::max)(20000, m_ses.m_peak_up_rate + 10000); + } + priority = (boost::uint64_t(m_est_reciprocation_rate) << 10) / upload_capacity; + } + else + { + priority = 1 + is_interesting() * 2 + m_requests_in_buffer.size(); + if (priority > 255) priority = 255; + priority += t->priority() << 8; + } + TORRENT_ASSERT(priority < 0xffff); // peers that we are not interested in are non-prioritized m_channel_state[upload_channel] = peer_info::bw_limit; diff --git a/src/session.cpp b/src/session.cpp index 19d1765f7..467ac1c16 100644 --- a/src/session.cpp +++ b/src/session.cpp @@ -204,7 +204,7 @@ namespace libtorrent set.active_limit = 2000; set.active_seeds = 2000; - set.auto_upload_slots = false; + set.choking_algorithm = session_settings::fixed_slots_choker; // in order to be able to deliver very high // upload rates, this should be able to cover diff --git a/src/session_impl.cpp b/src/session_impl.cpp index 6af04f258..abc329503 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -228,8 +228,11 @@ namespace aux { TORRENT_SETTING(boolean, free_torrent_hashes) TORRENT_SETTING(boolean, upnp_ignore_nonrouters) TORRENT_SETTING(integer, send_buffer_watermark) +#ifndef TORRENT_NO_DEPRECATE TORRENT_SETTING(boolean, auto_upload_slots) TORRENT_SETTING(boolean, auto_upload_slots_rate_based) +#endif + TORRENT_SETTING(integer, choking_algorithm) TORRENT_SETTING(boolean, use_parole_mode) TORRENT_SETTING(integer, cache_size) TORRENT_SETTING(integer, cache_buffer_chunk_size) @@ -438,6 +441,8 @@ namespace aux { , m_auto_scrape_time_scaler(180) , m_next_explicit_cache_torrent(0) , m_cache_rotation_timer(0) + , m_peak_up_rate(0) + , m_peak_down_rate(0) , m_incoming_connection(false) , m_created(time_now_hires()) , m_last_tick(m_created) @@ -1052,6 +1057,17 @@ namespace aux { || m_settings.low_prio_disk != s.low_prio_disk) update_disk_io_thread = true; +#ifndef TORRENT_NO_DEPRECATE + // support deprecated choker settings + if (s.choking_algorithm == session_settings::rate_based_choker) + { + if (s.auto_upload_slots && !s.auto_upload_slots_rate_based) + m_settings.choking_algorithm = session_settings::auto_expand_choker; + else if (!s.auto_upload_slots) + m_settings.choking_algorithm = session_settings::fixed_slots_choker; + } +#endif + // safety check if (m_settings.volatile_read_cache && (m_settings.suggest_mode == session_settings::suggest_read_cache @@ -1064,6 +1080,12 @@ namespace aux { m_settings.volatile_read_cache = false; } + if (m_settings.choking_algorithm != s.choking_algorithm) + { + // trigger recalculation of the unchoked peers + m_unchoke_time_scaler = 0; + } + // if queuing settings were changed, recalculate // queued torrents sooner if ((m_settings.active_downloads != s.active_downloads @@ -1089,7 +1111,12 @@ namespace aux { , performance_alert::too_many_optimistic_unchoke_slots)); } - if (!s.auto_upload_slots) m_allowed_upload_slots = m_max_uploads; + if (s.choking_algorithm == session_settings::fixed_slots_choker) + m_allowed_upload_slots = m_max_uploads; + else if (s.choking_algorithm == session_settings::auto_expand_choker + && m_allowed_upload_slots < m_max_uploads) + m_allowed_upload_slots = m_max_uploads; + // replace all occurances of '\n' with ' '. std::string::iterator i = m_settings.user_agent.begin(); while ((i = std::find(i, m_settings.user_agent.end(), '\n')) @@ -1915,6 +1942,9 @@ namespace aux { } } + m_peak_up_rate = (std::max)(m_stat.upload_rate(), m_peak_up_rate); + m_peak_down_rate = (std::max)(m_stat.download_rate(), m_peak_down_rate); + m_stat.second_tick(tick_interval_ms); TORRENT_ASSERT(least_recently_scraped == m_torrents.end() @@ -2394,8 +2424,29 @@ namespace aux { ++i; torrent* t = p->associated_torrent().lock().get(); policy::peer* pi = p->peer_info_struct(); + if (p->ignore_unchoke_slots() || t == 0 || pi == 0) continue; + if (m_settings.choking_algorithm == session_settings::bittyrant_choker) + { + if (!p->is_choked()) + { + policy::peer* pi = p->peer_info_struct(); + if (!p->has_peer_choked()) + { + // we're unchoked, we may want to lower our estimated + // reciprocation rate + p->decrease_est_reciprocation_rate(); + } + else + { + // we've unchoked this peer, and it hasn't reciprocated + // we may want to increase our estimated reciprocation rate + p->increase_est_reciprocation_rate(); + } + } + } + if (!p->is_peer_interested() || p->is_disconnecting() || p->is_connecting() @@ -2417,8 +2468,7 @@ namespace aux { peers.push_back(p.get()); } - if (m_settings.auto_upload_slots_rate_based - && m_settings.auto_upload_slots) + if (m_settings.choking_algorithm == session_settings::rate_based_choker) { m_allowed_upload_slots = 0; std::sort(peers.begin(), peers.end() @@ -2460,17 +2510,26 @@ namespace aux { ++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 - // be unchoked - std::sort(peers.begin(), peers.end() - , bind(&peer_connection::unchoke_compare, _1, _2)); + if (m_settings.choking_algorithm == session_settings::bittyrant_choker) + { + // if we're using the bittyrant choker, sort peers by their return + // on investment. i.e. download rate / upload rate + std::sort(peers.begin(), peers.end() + , bind(&peer_connection::bittyrant_unchoke_compare, _1, _2)); + } + else + { + // 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 + // be unchoked + std::sort(peers.begin(), peers.end() + , bind(&peer_connection::unchoke_compare, _1, _2)); + } // auto unchoke int upload_limit = m_bandwidth_channel[peer_connection::upload_channel]->throttle(); - if (!m_settings.auto_upload_slots_rate_based - && m_settings.auto_upload_slots + if (m_settings.choking_algorithm == session_settings::auto_expand_choker && upload_limit > 0) { // if our current upload rate is less than 90% of our @@ -2494,9 +2553,25 @@ namespace aux { int num_opt_unchoke = m_settings.num_optimistic_unchoke_slots; if (num_opt_unchoke == 0) num_opt_unchoke = (std::max)(1, m_allowed_upload_slots / 5); - // reserve one upload slot for optimistic unchokes + // reserve some upload slots for optimistic unchokes int unchoke_set_size = m_allowed_upload_slots - num_opt_unchoke; + int upload_capacity_left = 0; + if (m_settings.choking_algorithm == session_settings::bittyrant_choker) + { + upload_capacity_left = m_upload_channel.throttle(); + if (upload_capacity_left == 0) + { + // we don't know at what rate we can upload. If we have a + // measurement of the peak, use that + 10kB/s, otherwise + // assume 20 kB/s + upload_capacity_left = (std::max)(20000, m_peak_up_rate + 10000); + if (m_alerts.should_post()) + m_alerts.post_alert(performance_alert(torrent_handle() + , performance_alert::bittyrant_with_no_uplimit)); + } + } + m_num_unchoked = 0; // go through all the peers and unchoke the first ones and choke // all the other ones. @@ -2508,12 +2583,28 @@ namespace aux { TORRENT_ASSERT(!p->ignore_unchoke_slots()); // this will update the m_uploaded_at_last_unchoke + // #error this should be called for all peers! p->reset_choke_counters(); torrent* t = p->associated_torrent().lock().get(); TORRENT_ASSERT(t); - if (unchoke_set_size > 0) + + // if this peer should be unchoked depends on different things + // in different unchoked schemes + bool unchoke = false; + if (m_settings.choking_algorithm == session_settings::bittyrant_choker) { + unchoke = p->est_reciprocation_rate() <= upload_capacity_left; + } + else + { + unchoke = unchoke_set_size > 0; + } + + if (unchoke) + { + upload_capacity_left -= p->est_reciprocation_rate(); + // yes, this peer should be unchoked if (p->is_choked()) { @@ -3565,7 +3656,7 @@ namespace aux { std::set unique_peers; TORRENT_ASSERT(m_max_connections > 0); TORRENT_ASSERT(m_max_uploads >= 0); - if (!m_settings.auto_upload_slots_rate_based || !m_settings.auto_upload_slots) + if (m_settings.choking_algorithm == session_settings::auto_expand_choker) TORRENT_ASSERT(m_allowed_upload_slots >= m_max_uploads); int unchokes = 0; int num_optimistic = 0; diff --git a/test/test_auto_unchoke.cpp b/test/test_auto_unchoke.cpp index 345287eb6..63d9787d9 100644 --- a/test/test_auto_unchoke.cpp +++ b/test/test_auto_unchoke.cpp @@ -17,10 +17,10 @@ void test_swarm() session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49010, 50000), "0.0.0.0", 0); session ses3(fingerprint("LT", 0, 1, 0, 0), std::make_pair(50010, 51000), "0.0.0.0", 0); - ses1.set_severity_level(alert::debug); - ses2.set_severity_level(alert::debug); - ses3.set_severity_level(alert::debug); - + ses1.set_alert_mask(alert::all_categories); + ses2.set_alert_mask(alert::all_categories); + ses3.set_alert_mask(alert::all_categories); + // this is to avoid everything finish from a single peer // immediately. To make the swarm actually connect all // three peers before finishing. @@ -35,8 +35,7 @@ void test_swarm() session_settings settings; settings.allow_multiple_connections_per_ip = true; settings.ignore_limits_on_local_network = false; - settings.auto_upload_slots = true; - settings.auto_upload_slots_rate_based = false; + settings.choking_algorithm = session_settings::auto_expand_choker; ses1.set_settings(settings); ses2.set_settings(settings); ses3.set_settings(settings);