diff --git a/include/libtorrent/torrent.hpp b/include/libtorrent/torrent.hpp index 968e3d96a..4853ec740 100644 --- a/include/libtorrent/torrent.hpp +++ b/include/libtorrent/torrent.hpp @@ -1490,10 +1490,10 @@ namespace libtorrent // is is disabled while paused and checking files bool m_announcing:1; - // this is true while the tracker deadline timer + // this is > 0 while the tracker deadline timer // is in use. i.e. one or more trackers are waiting // for a reannounce - bool m_waiting_tracker:1; + boost::int8_t m_waiting_tracker; // ---- diff --git a/simulation/test_tracker.cpp b/simulation/test_tracker.cpp index f887801da..c793fa851 100644 --- a/simulation/test_tracker.cpp +++ b/simulation/test_tracker.cpp @@ -33,6 +33,9 @@ POSSIBILITY OF SUCH DAMAGE. #include "test.hpp" #include "settings.hpp" #include "setup_swarm.hpp" +#include "setup_transfer.hpp" // for addr() +#include "utils.hpp" // for print_alerts +#include "create_torrent.hpp" #include "simulator/simulator.hpp" #include "simulator/http_server.hpp" #include "simulator/utils.hpp" @@ -52,6 +55,12 @@ using chrono::duration_cast; // seconds const int duration = 10000; +template +boost::shared_ptr clone_ptr(boost::shared_ptr const& ptr) +{ + return boost::make_shared(*ptr); +} + void test_interval(int interval) { using sim::asio::ip::address_v4; @@ -746,7 +755,7 @@ boost::shared_ptr make_torrent(bool priv) { file_storage fs; fs.add_file("foobar", 13241); - create_torrent ct(fs); + lt::create_torrent ct(fs); ct.add_tracker("http://tracker.com:8080/announce"); @@ -907,6 +916,112 @@ TORRENT_TEST(tracker_user_agent_privacy_mode_private_torrent) TEST_EQUAL(got_announce, true); } +// This test sets up two peers, one seed an one downloader. The downloader has +// two trackers, both in tier 0. The behavior we expect is that it picks one of +// the trackers at random and announces to it. Since both trackers are working, +// it should not announce to the tracker it did not initially pick. + +// #error parameterize this test over adding the trackers into different tiers +// and setting "announce_to_all_tiers" + +TORRENT_TEST(tracker_tiers) +{ + using namespace libtorrent; + + char const* peer0_ip = "50.0.0.1"; + char const* peer1_ip = "50.0.0.2"; + + using asio::ip::address; + address peer0 = addr(peer0_ip); + address peer1 = addr(peer1_ip); + + // setup the simulation + sim::default_config network_cfg; + sim::simulation sim{network_cfg}; + sim::asio::io_service ios0 { sim, peer0 }; + sim::asio::io_service ios1 { sim, peer1 }; + + sim::asio::io_service tracker1(sim, address_v4::from_string("3.0.0.1")); + sim::asio::io_service tracker2(sim, address_v4::from_string("3.0.0.2")); + sim::http_server http1(tracker1, 8080); + sim::http_server http2(tracker2, 8080); + + bool received_announce[2] = {false, false}; + http1.register_handler("/announce" + , [&](std::string method, std::string req + , std::map&) + { + received_announce[0] = true; + std::string ret = "d8:intervali60e5:peers0:e"; + return sim::send_response(200, "OK", ret.size()) + ret; + }); + + http2.register_handler("/announce" + , [&](std::string method, std::string req + , std::map&) + { + received_announce[1] = true; + std::string ret = "d8:intervali60e5:peers0:e"; + return sim::send_response(200, "OK", ret.size()) + ret; + }); + + lt::session_proxy zombie[2]; + + // setup settings pack to use for the session (customization point) + lt::settings_pack pack = settings(); + // create session + std::shared_ptr ses[2]; + pack.set_str(settings_pack::listen_interfaces, peer0_ip + std::string(":6881")); + ses[0] = std::make_shared(pack, ios0); + + pack.set_str(settings_pack::listen_interfaces, peer1_ip + std::string(":6881")); + ses[1] = std::make_shared(pack, ios1); + + // only monitor alerts for session 0 (the downloader) + print_alerts(*ses[0], [=](lt::session& ses, lt::alert const* a) { + if (auto ta = alert_cast(a)) + { + ta->handle.connect_peer(lt::tcp::endpoint(peer1, 6881)); + } + }); + + print_alerts(*ses[1]); + + // the first peer is a downloader, the second peer is a seed + lt::add_torrent_params params = ::create_torrent(1); + auto ti2 = clone_ptr(params.ti); + params.flags &= ~lt::add_torrent_params::flag_auto_managed; + params.flags &= ~lt::add_torrent_params::flag_paused; + + // These trackers are in the same tier. libtorrent is expected to pick one at + // random and stick to it, never announce to the other one. + params.ti->add_tracker("http://3.0.0.1:8080/announce", 0); + params.ti->add_tracker("http://3.0.0.2:8080/announce", 0); + params.save_path = save_path(0); + ses[0]->async_add_torrent(params); + + params.ti = ti2; + params.save_path = save_path(1); + ses[1]->async_add_torrent(params); + + sim::timer t(sim, lt::minutes(30), [&](boost::system::error_code const& ec) + { + TEST_CHECK(received_announce[0] != received_announce[1]); + TEST_CHECK(ses[0]->get_torrents()[0].status().is_seeding); + TEST_CHECK(ses[1]->get_torrents()[0].status().is_seeding); + + // shut down + int idx = 0; + for (auto& s : ses) + { + zombie[idx++] = s->abort(); + s.reset(); + } + }); + + sim.run(); +} + // TODO: test external IP // TODO: test with different queuing settings // TODO: test when a torrent transitions from downloading to finished and diff --git a/src/announce_entry.cpp b/src/announce_entry.cpp index d94e17f29..554e05019 100644 --- a/src/announce_entry.cpp +++ b/src/announce_entry.cpp @@ -116,9 +116,10 @@ namespace libtorrent { // if we're a seed and we haven't sent a completed // event, we need to let this announce through - bool need_send_complete = is_seed && !complete_sent; + bool const need_send_complete = is_seed && !complete_sent; - return now >= next_announce + // add some slack here for rounding errors + return now + seconds(1) >= next_announce && (now >= min_announce || need_send_complete) && (fails < fail_limit || fail_limit == 0) && !updating; diff --git a/src/torrent.cpp b/src/torrent.cpp index aef33a964..d606fbe41 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -247,7 +247,7 @@ namespace libtorrent , m_files_checked(false) , m_storage_mode(p.storage_mode) , m_announcing(false) - , m_waiting_tracker(false) + , m_waiting_tracker(0) , m_active_time(0) , m_last_working_tracker(-1) , m_finished_time(0) @@ -2973,7 +2973,8 @@ namespace { #endif boost::shared_ptr t = p.lock(); if (!t) return; - t->m_waiting_tracker = false; + TORRENT_ASSERT(t->m_waiting_tracker > 0); + --t->m_waiting_tracker; if (e) return; t->on_tracker_announce(); @@ -3224,7 +3225,7 @@ namespace { req.num_want = (req.event == tracker_request::stopped) ? 0 : settings().get_int(settings_pack::num_want); - time_point now = aux::time_now(); + time_point const now = aux::time_now(); // the tier is kept as INT_MAX until we find the first // tracker that works, then it's set to that tracker's @@ -10205,7 +10206,7 @@ namespace { } else { - time_point next_tracker_announce = (std::max)(i->next_announce, i->min_announce); + time_point const next_tracker_announce = std::max(i->next_announce, i->min_announce); if (next_tracker_announce < next_announce && (!found_working || i->is_working())) next_announce = next_tracker_announce; @@ -10229,7 +10230,7 @@ namespace { // if m_waiting_tracker is false, expires_at() is undefined if (m_waiting_tracker && m_tracker_timer.expires_at() == next_announce) return; - m_waiting_tracker = true; + ++m_waiting_tracker; error_code ec; boost::weak_ptr self(shared_from_this());