diff --git a/include/libtorrent/aux_/allocate_resources_impl.hpp b/include/libtorrent/aux_/allocate_resources_impl.hpp new file mode 100644 index 000000000..990dcad96 --- /dev/null +++ b/include/libtorrent/aux_/allocate_resources_impl.hpp @@ -0,0 +1,231 @@ +/* + +Copyright (c) 2003, Magnus Jonsson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef TORRENT_ALLOCATE_RESOURCES_IMPL_HPP_INCLUDED +#define TORRENT_ALLOCATE_RESOURCES_IMPL_HPP_INCLUDED + +#include +#include + +#include + +#include "libtorrent/resource_request.hpp" +#include "libtorrent/peer_id.hpp" +#include "libtorrent/socket.hpp" +#include "libtorrent/size_type.hpp" + +namespace libtorrent +{ + + int saturated_add(int a, int b); + + namespace aux + { + // give num_resources to r, + // return how how many were actually accepted. + inline int give(resource_request& r, int num_resources) + { + assert(num_resources >= 0); + assert(r.given <= r.max); + + int accepted = std::min(num_resources, r.max - r.given); + assert(accepted >= 0); + + r.given += accepted; + assert(r.given <= r.max); + + return accepted; + } + +#ifndef NDEBUG + + template + class allocate_resources_contract_check + { + int m_resources; + It m_start; + It m_end; + resource_request T::* m_res; + + public: + allocate_resources_contract_check( + int resources + , It start + , It end + , resource_request T::* res) + : m_resources(resources) + , m_start(start) + , m_end(end) + , m_res(res) + { + assert(m_resources >= 0); + for (It i = m_start, end(m_end); i != end; ++i) + { + assert(((*i).*m_res).max >= 0); + assert(((*i).*m_res).given >= 0); + } + } + + ~allocate_resources_contract_check() + { + int sum_given = 0; + int sum_max = 0; + int sum_min = 0; + for (It i = m_start, end(m_end); i != end; ++i) + { + assert(((*i).*m_res).max >= 0); + assert(((*i).*m_res).min >= 0); + assert(((*i).*m_res).max >= ((*i).*m_res).min); + assert(((*i).*m_res).given >= 0); + assert(((*i).*m_res).given <= ((*i).*m_res).max); + + sum_given = saturated_add(sum_given, ((*i).*m_res).given); + sum_max = saturated_add(sum_max, ((*i).*m_res).max); + sum_min = saturated_add(sum_min, ((*i).*m_res).min); + } + assert(sum_given == std::min(std::max(m_resources, sum_min), sum_max)); + } + }; + +#endif + + template + void allocate_resources_impl( + int resources + , It start + , It end + , resource_request T::* res) + { + assert(resources >= 0); + #ifndef NDEBUG + allocate_resources_contract_check contract_check( + resources + , start + , end + , res); + #endif + + if (resources == resource_request::inf) + { + // No competition for resources. + // Just give everyone what they want. + for (It i = start; i != end; ++i) + { + ((*i).*res).given = ((*i).*res).max; + } + return; + } + + // Resources are scarce + + int sum_max = 0; + int sum_min = 0; + for (It i = start; i != end; ++i) + { + sum_max = saturated_add(sum_max, ((*i).*res).max); + assert(((*i).*res).min < resource_request::inf); + assert(((*i).*res).min >= 0); + assert(((*i).*res).min <= ((*i).*res).max); + sum_min += ((*i).*res).min; + ((*i).*res).given = ((*i).*res).min; + } + + if (resources == 0 || sum_max == 0) + return; + + resources = std::max(resources, sum_min); + int resources_to_distribute = std::min(resources, sum_max) - sum_min; + assert(resources_to_distribute >= 0); +#ifndef NDEBUG + int prev_resources_to_distribute = resources_to_distribute; +#endif + while (resources_to_distribute > 0) + { + size_type total_used = 0; + size_type max_used = 0; + for (It i = start; i != end; ++i) + { + resource_request& r = (*i).*res; + if(r.given == r.max) continue; + + assert(r.given < r.max); + + max_used = std::max(max_used, (size_type)r.used + 1); + total_used += (size_type)r.used + 1; + } + + size_type kNumer = resources_to_distribute; + size_type kDenom = total_used; + assert(kNumer >= 0); + assert(kDenom >= 0); + assert(kNumer <= std::numeric_limits::max()); + assert(total_used < std::numeric_limits::max()); + + if (kNumer * max_used <= kDenom) + { + kNumer = 1; + kDenom = max_used; + assert(kDenom >= 0); + assert(kDenom <= std::numeric_limits::max()); + } + + for (It i = start; i != end && resources_to_distribute > 0; ++i) + { + resource_request& r = (*i).*res; + if (r.given == r.max) continue; + + assert(r.given < r.max); + + size_type used = (size_type)r.used + 1; + if (used < 1) used = 1; + size_type to_give = used * kNumer / kDenom; + if (to_give > resources_to_distribute) + to_give = resources_to_distribute; + assert(to_give >= 0); + assert(to_give <= resources_to_distribute); + resources_to_distribute -= give(r, (int)to_give); + assert(resources_to_distribute >= 0); + } + + assert(resources_to_distribute >= 0); + assert(resources_to_distribute < prev_resources_to_distribute); +#ifndef NDEBUG + prev_resources_to_distribute = resources_to_distribute; +#endif + } + } + + } // namespace libtorrent::aux +} + + +#endif diff --git a/include/libtorrent/resource_request.hpp b/include/libtorrent/resource_request.hpp index 5cb0fb76e..046b49df9 100755 --- a/include/libtorrent/resource_request.hpp +++ b/include/libtorrent/resource_request.hpp @@ -56,6 +56,13 @@ namespace libtorrent , given(0) {} + resource_request(int used_, int min_, int max_, int given_) + : used(used_) + , min(min_) + , max(max_) + , given(given_) + {} + int left() const { assert(given <= max); @@ -67,7 +74,7 @@ namespace libtorrent static const int inf = boost::integer_traits::const_max; - // I'm right now actively using: + // right now I'm actively using this amount int used; // given cannot be smaller than min @@ -75,7 +82,7 @@ namespace libtorrent int min; int max; - // Reply: Okay, you're allowed to use this much (a compromise): + // Reply: Okay, you're allowed to use this amount (a compromise): int given; }; } diff --git a/src/allocate_resources.cpp b/src/allocate_resources.cpp index e06b2417d..7d08b036d 100644 --- a/src/allocate_resources.cpp +++ b/src/allocate_resources.cpp @@ -52,6 +52,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/size_type.hpp" #include "libtorrent/peer_connection.hpp" #include "libtorrent/torrent.hpp" +#include "libtorrent/aux_/allocate_resources_impl.hpp" #include #include @@ -81,203 +82,6 @@ namespace libtorrent return int(sum); } - namespace - { - // give num_resources to r, - // return how how many were actually accepted. - int give(resource_request& r, int num_resources) - { - assert(num_resources >= 0); - assert(r.given <= r.max); - - int accepted = std::min(num_resources, r.max - r.given); - assert(accepted >= 0); - - r.given += accepted; - assert(r.given <= r.max); - - return accepted; - } - -#ifndef NDEBUG - - template - class allocate_resources_contract_check - { - int m_resources; - It m_start; - It m_end; - resource_request T::* m_res; - - public: - allocate_resources_contract_check( - int resources - , It start - , It end - , resource_request T::* res) - : m_resources(resources) - , m_start(start) - , m_end(end) - , m_res(res) - { - assert(m_resources >= 0); - for (It i = m_start, end(m_end); i != end; ++i) - { - assert(((*i).*m_res).max >= 0); - assert(((*i).*m_res).given >= 0); - } - } - - ~allocate_resources_contract_check() - { - int sum_given = 0; - int sum_max = 0; - int sum_min = 0; - for (It i = m_start, end(m_end); i != end; ++i) - { - assert(((*i).*m_res).max >= 0); - assert(((*i).*m_res).min >= 0); - assert(((*i).*m_res).max >= ((*i).*m_res).min); - assert(((*i).*m_res).given >= 0); - assert(((*i).*m_res).given <= ((*i).*m_res).max); - - sum_given = saturated_add(sum_given, ((*i).*m_res).given); - sum_max = saturated_add(sum_max, ((*i).*m_res).max); - sum_min = saturated_add(sum_min, ((*i).*m_res).min); - } - assert(sum_given == std::min(std::max(m_resources, sum_min), sum_max)); - } - }; - -#endif - - template - void allocate_resources_impl( - int resources - , It start - , It end - , resource_request T::* res) - { - assert(resources >= 0); - #ifndef NDEBUG - allocate_resources_contract_check contract_check( - resources - , start - , end - , res); - #endif - - if (resources == resource_request::inf) - { - // No competition for resources. - // Just give everyone what they want. - for (It i = start; i != end; ++i) - { - ((*i).*res).given = ((*i).*res).max; - } - return; - } - - // Resources are scarce - - int sum_max = 0; - int sum_min = 0; - for (It i = start; i != end; ++i) - { - sum_max = saturated_add(sum_max, ((*i).*res).max); - assert(((*i).*res).min < resource_request::inf); - assert(((*i).*res).min >= 0); - assert(((*i).*res).min <= ((*i).*res).max); - sum_min += ((*i).*res).min; - ((*i).*res).given = ((*i).*res).min; - } - - if (resources == 0 || sum_max == 0) - return; - - resources = std::max(resources, sum_min); - int resources_to_distribute = std::min(resources, sum_max) - sum_min; - assert(resources_to_distribute >= 0); -#ifndef NDEBUG - int prev_resources_to_distribute = resources_to_distribute; -#endif - while (resources_to_distribute > 0) - { - size_type total_used = 0; - size_type max_used = 0; - for (It i = start; i != end; ++i) - { - resource_request& r = (*i).*res; - if(r.given == r.max) continue; - - assert(r.given < r.max); - - max_used = std::max(max_used, (size_type)r.used + 1); - total_used += (size_type)r.used + 1; - } - - size_type kNumer = resources_to_distribute; - size_type kDenom = total_used; - assert(kNumer >= 0); - assert(kDenom >= 0); - assert(kNumer <= std::numeric_limits::max()); - assert(total_used < std::numeric_limits::max()); - - if (kNumer * max_used <= kDenom) - { - kNumer = 1; - kDenom = max_used; - assert(kDenom >= 0); - assert(kDenom <= std::numeric_limits::max()); - } - - for (It i = start; i != end && resources_to_distribute > 0; ++i) - { - resource_request& r = (*i).*res; - if (r.given == r.max) continue; - - assert(r.given < r.max); - - size_type used = (size_type)r.used + 1; - if (used < 1) used = 1; - size_type to_give = used * kNumer / kDenom; - if (to_give > resources_to_distribute) - to_give = resources_to_distribute; - assert(to_give >= 0); - assert(to_give <= resources_to_distribute); - resources_to_distribute -= give(r, (int)to_give); - assert(resources_to_distribute >= 0); - } - - assert(resources_to_distribute >= 0); - assert(resources_to_distribute < prev_resources_to_distribute); -#ifndef NDEBUG - prev_resources_to_distribute = resources_to_distribute; -#endif - } - } - - peer_connection& pick_peer( - std::pair - , boost::intrusive_ptr > const& p) - { - return *p.second; - } - - peer_connection& pick_peer2( - std::pair const& p) - { - return *p.second; - } - - torrent& deref(std::pair > const& p) - { - return *p.second; - } - - - } // namespace anonymous - #if defined(_MSC_VER) && _MSC_VER < 1310 namespace detail @@ -319,7 +123,7 @@ namespace libtorrent , std::map >& c , resource_request torrent::* res) { - allocate_resources_impl( + aux::allocate_resources_impl( resources , detail::iterator_wrapper(c.begin()) , detail::iterator_wrapper(c.end()) @@ -331,7 +135,7 @@ namespace libtorrent , std::map& c , resource_request peer_connection::* res) { - allocate_resources_impl( + aux::allocate_resources_impl( resources , detail::iterator_wrapper2(c.begin()) , detail::iterator_wrapper2(c.end()) @@ -340,6 +144,27 @@ namespace libtorrent #else + namespace aux + { + peer_connection& pick_peer( + std::pair + , boost::intrusive_ptr > const& p) + { + return *p.second; + } + + peer_connection& pick_peer2( + std::pair const& p) + { + return *p.second; + } + + torrent& deref(std::pair > const& p) + { + return *p.second; + } + } + void allocate_resources( int resources , std::map >& c @@ -349,10 +174,10 @@ namespace libtorrent typedef std::pair > in_param; typedef boost::transform_iterator new_iter; - allocate_resources_impl( + aux::allocate_resources_impl( resources - , new_iter(c.begin(), &deref) - , new_iter(c.end(), &deref) + , new_iter(c.begin(), &aux::deref) + , new_iter(c.end(), &aux::deref) , res); } @@ -365,10 +190,10 @@ namespace libtorrent typedef std::pair in_param; typedef boost::transform_iterator new_iter; - allocate_resources_impl( + aux::allocate_resources_impl( resources - , new_iter(c.begin(), &pick_peer2) - , new_iter(c.end(), &pick_peer2) + , new_iter(c.begin(), &aux::pick_peer2) + , new_iter(c.end(), &aux::pick_peer2) , res); } #endif diff --git a/test/Jamfile b/test/Jamfile index d5a28f407..30f05c9de 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -17,5 +17,6 @@ test-suite libtorrent : [ run test_ip_filter.cpp ] [ run test_hasher.cpp ] [ run test_metadata_extension.cpp ] + [ run test_allocate_resources.cpp ] ; diff --git a/test/test_allocate_resources.cpp b/test/test_allocate_resources.cpp new file mode 100644 index 000000000..4f00f19c3 --- /dev/null +++ b/test/test_allocate_resources.cpp @@ -0,0 +1,57 @@ +#include "libtorrent/aux_/allocate_resources_impl.hpp" +#include + +#include "test.hpp" + +using namespace libtorrent; + +struct resource_entry +{ + resource_entry(resource_request r_): r(r_) {} + resource_request r; +}; + +void fill_client_vector(std::vector& v) +{ + v.push_back(resource_request(5000, 20, 20000, 10000)); + v.push_back(resource_request(9000, 20, 20000, 10000)); + v.push_back(resource_request(8000, 20, 20000, 10000)); + v.push_back(resource_request(7000, 20, 20000, 10000)); + v.push_back(resource_request(5000, 20, 20000, 10000)); + v.push_back(resource_request(8000, 20, 20000, 10000)); +} + +void check_client_vec(std::vector const& v, int resources) +{ + int sum = 0; + int min_sum = 0; + for (std::vector::const_iterator i = v.begin() + , end(v.end()); i != end; ++i) + { + TEST_CHECK(i->r.given >= i->r.min); + TEST_CHECK(i->r.given <= i->r.max); + sum += i->r.given; + min_sum += i->r.min; + } + TEST_CHECK(sum <= (std::max)(resources, min_sum)); +} + +int test_main() +{ + using namespace libtorrent; + + std::vector clients; + fill_client_vector(clients); + + using aux::allocate_resources_impl; + + allocate_resources_impl(20, clients.begin(), clients.end(), &resource_entry::r); + check_client_vec(clients, 20); + + fill_client_vector(clients); + allocate_resources_impl(20000, clients.begin(), clients.end(), &resource_entry::r); + check_client_vec(clients, 20000); + + return 0; +} +