From e4de1fc8b1c2bfbfbdade2970fe28db17289e3fd Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Sat, 30 Oct 2010 08:36:18 +0000 Subject: [PATCH] graceful disconnect mode which finishes transactions before disconnecting peers --- ChangeLog | 1 + bindings/python/src/torrent_handle.cpp | 6 ++- docs/manual.rst | 12 ++++- examples/client_test.cpp | 11 ++--- include/libtorrent/peer_connection.hpp | 4 ++ include/libtorrent/torrent.hpp | 15 ++++-- include/libtorrent/torrent_handle.hpp | 3 +- src/peer_connection.cpp | 21 ++++++-- src/session_impl.cpp | 6 ++- src/torrent.cpp | 66 ++++++++++++++++++++++---- src/torrent_handle.cpp | 4 +- 11 files changed, 118 insertions(+), 31 deletions(-) diff --git a/ChangeLog b/ChangeLog index 0a7181a63..13c52afa1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,4 @@ + * graceful peer disconnect mode which finishes transactions before disconnecting peers * support chunked encoding for web seeds (only for BEP 19, web seeds) * optimized session startup time * support SSL for web seeds, through all proxies diff --git a/bindings/python/src/torrent_handle.cpp b/bindings/python/src/torrent_handle.cpp index 1efbcc5d6..c01fa2c4c 100644 --- a/bindings/python/src/torrent_handle.cpp +++ b/bindings/python/src/torrent_handle.cpp @@ -299,7 +299,7 @@ void bind_torrent_handle() .def("is_seed", _(&torrent_handle::is_seed)) .def("is_finished", _(&torrent_handle::is_finished)) .def("is_paused", _(&torrent_handle::is_paused)) - .def("pause", _(&torrent_handle::pause)) + .def("pause", _(&torrent_handle::pause), arg("flags") = 0) .def("resume", _(&torrent_handle::resume)) .def("clear_error", _(&torrent_handle::clear_error)) .def("set_priority", _(&torrent_handle::set_priority)) @@ -366,6 +366,10 @@ void bind_torrent_handle() #endif ; + enum_("pause_flags_t") + .value("graceful_pause", torrent_handle::graceful_pause) + ; + enum_("save_resume_flags_t") .value("flush_disk_cache", torrent_handle::flush_disk_cache) ; diff --git a/docs/manual.rst b/docs/manual.rst index c61958c93..07ae370d9 100644 --- a/docs/manual.rst +++ b/docs/manual.rst @@ -1955,7 +1955,8 @@ Its declaration looks like this:: void use_interface(char const* net_interface) const; - void pause() const; + enum pause_flags_t { graceful_pause = 1 }; + void pause(int flags = 0) const; void resume() const; bool is_paused() const; bool is_seed() const; @@ -2393,7 +2394,8 @@ pause() resume() is_paused() :: - void pause() const; + enum pause_flags_t { graceful_pause = 1 }; + void pause(int flags) const; void resume() const; bool is_paused() const; @@ -2403,6 +2405,12 @@ all potential (not connected) peers. You can use ``is_paused()`` to determine if is currently paused. Torrents may be paused automatically if there is a file error (e.g. disk full) or something similar. See file_error_alert_. +The ``flags`` argument to pause can be set to ``torrent_handle::graceful_pause`` which will +delay the disconnect of peers that we're still downloading outstanding requests from. The torrent +will not accept any more requests and will disconnect all idle peers. As soon as a peer is +done transferring the blocks that were requested from it, it is disconnected. This is a graceful +shut down of the torrent in the sense that no downloaded bytes are wasted. + torrents that are auto-managed may be automatically resumed again. It does not make sense to pause an auto-managed torrent without making it not automanaged first. Torrents are auto-managed by default when added to the session. For more information, see queuing_. diff --git a/examples/client_test.cpp b/examples/client_test.cpp index 84d7009c2..ca01d8ac5 100644 --- a/examples/client_test.cpp +++ b/examples/client_test.cpp @@ -1222,7 +1222,7 @@ int main(int argc, char* argv[]) else { h.auto_managed(false); - h.pause(); + h.pause(torrent_handle::graceful_pause); } // the alert handler for save_resume_data_alert // will save it to disk @@ -1336,7 +1336,9 @@ int main(int argc, char* argv[]) out += str; } - if (h.is_paused()) out += esc("34"); + torrent_status s = h.status(); + + if (s.paused) out += esc("34"); else out += esc("37"); std::string name = h.name(); @@ -1344,9 +1346,6 @@ int main(int argc, char* argv[]) snprintf(str, sizeof(str), "%-40s %s ", name.c_str(), term); out += str; - torrent_status s = h.status(); - - bool paused = h.is_paused(); bool auto_managed = h.is_auto_managed(); bool sequential_download = h.is_sequential_download(); @@ -1373,7 +1372,7 @@ int main(int argc, char* argv[]) { snprintf(str, sizeof(str), "%-13s down: (%s%s%s) up: %s%s%s (%s%s%s) swarm: %4d:%4d" " bw queue: (%d|%d) all-time (Rx: %s%s%s Tx: %s%s%s) seed rank: %x %c%s\n" - , (paused && !auto_managed)?"paused":(paused && auto_managed)?"queued":state_str[s.state] + , (s.paused && !auto_managed)?"paused":(s.paused && auto_managed)?"queued":state_str[s.state] , esc("32"), add_suffix(s.total_download).c_str(), term , esc("31"), add_suffix(s.upload_rate, "/s").c_str(), term , esc("31"), add_suffix(s.total_upload).c_str(), term diff --git a/include/libtorrent/peer_connection.hpp b/include/libtorrent/peer_connection.hpp index f92a8bec7..1bb317730 100644 --- a/include/libtorrent/peer_connection.hpp +++ b/include/libtorrent/peer_connection.hpp @@ -295,6 +295,8 @@ namespace libtorrent std::vector const& request_queue() const; std::vector const& upload_queue() const; + void clear_request_queue(); + // estimate of how long it will take until we have // received all piece requests that we have sent // if extra_bytes is specified, it will include those @@ -539,6 +541,8 @@ namespace libtorrent bool has_country() const { return m_country[0] != 0; } #endif + int outstanding_bytes() const { return m_outstanding_bytes; } + int send_buffer_size() const { return m_send_buffer.size(); } diff --git a/include/libtorrent/torrent.hpp b/include/libtorrent/torrent.hpp index 76ec1027a..815a60992 100644 --- a/include/libtorrent/torrent.hpp +++ b/include/libtorrent/torrent.hpp @@ -151,8 +151,10 @@ namespace libtorrent void set_share_mode(bool s); bool share_mode() const { return m_share_mode; } + bool graceful_pause() const { return m_graceful_pause_mode; } + void set_upload_mode(bool b); - bool upload_mode() const { return m_upload_mode; } + bool upload_mode() const { return m_upload_mode || m_graceful_pause_mode; } bool is_upload_only() const { return (is_finished() || upload_mode()) && !super_seeding(); } @@ -222,9 +224,9 @@ namespace libtorrent bool has_error() const { return m_error; } void flush_cache(); - void pause(); + void pause(bool graceful = false); void resume(); - void set_allow_peers(bool b); + void set_allow_peers(bool b, bool graceful_pause = false); void set_announce_to_dht(bool b) { m_announce_to_dht = b; } void set_announce_to_trackers(bool b) { m_announce_to_trackers = b; } void set_announce_to_lsd(bool b) { m_announce_to_lsd = b; } @@ -235,7 +237,7 @@ namespace libtorrent bool is_paused() const; bool allows_peers() const { return m_allow_peers; } - bool is_torrent_paused() const { return !m_allow_peers; } + bool is_torrent_paused() const { return !m_allow_peers || m_graceful_pause_mode; } void force_recheck(); void save_resume_data(int flags); @@ -1224,6 +1226,11 @@ namespace libtorrent // round-robin index into m_interfaces mutable boost::uint8_t m_interface_index; + + // set to true when this torrent has been paused but + // is waiting to finish all current download requests + // before actually closing all connections + bool m_graceful_pause_mode:1; }; } diff --git a/include/libtorrent/torrent_handle.hpp b/include/libtorrent/torrent_handle.hpp index 1fdf6f6b9..d5da1bf76 100644 --- a/include/libtorrent/torrent_handle.hpp +++ b/include/libtorrent/torrent_handle.hpp @@ -469,7 +469,8 @@ namespace libtorrent bool is_seed() const; bool is_finished() const; bool is_paused() const; - void pause() const; + enum pause_flags_t { graceful_pause = 1 }; + void pause(int flags = 0) const; void resume() const; void set_upload_mode(bool b) const; void set_share_mode(bool b) const; diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp index 232467a0f..40dc85bd9 100644 --- a/src/peer_connection.cpp +++ b/src/peer_connection.cpp @@ -1235,9 +1235,6 @@ namespace libtorrent { INVARIANT_CHECK; - boost::shared_ptr t = m_torrent.lock(); - TORRENT_ASSERT(t); - #ifndef TORRENT_DISABLE_EXTENSIONS for (extension_list_t::iterator i = m_extensions.begin() , end(m_extensions.end()); i != end; ++i) @@ -1252,6 +1249,14 @@ namespace libtorrent #endif m_peer_choked = true; + clear_request_queue(); + } + + void peer_connection::clear_request_queue() + { + boost::shared_ptr t = m_torrent.lock(); + TORRENT_ASSERT(t); + // clear the requests that haven't been sent yet if (peer_info_struct() == 0 || !peer_info_struct()->on_parole) { @@ -1269,7 +1274,6 @@ namespace libtorrent m_request_queue.clear(); m_queued_time_critical = 0; } - } bool match_request(peer_request const& r, piece_block const& b, int block_size) @@ -3108,6 +3112,15 @@ namespace libtorrent if (m_disconnecting) return; + if (t->graceful_pause() && m_outstanding_bytes == 0) + { +#ifdef TORRENT_VERBOSE_LOGGING + (*m_logger) << time_now_string() << " *** GRACEFUL PAUSE [ NO MORE DOWNLOAD ]\n"; +#endif + disconnect(errors::torrent_paused); + return; + } + if ((int)m_download_queue.size() >= m_desired_queue_size || t->upload_mode()) return; diff --git a/src/session_impl.cpp b/src/session_impl.cpp index 69493385c..7c1c15944 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -2647,7 +2647,8 @@ namespace aux { #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING || defined TORRENT_LOGGING t->log_to_all_peers(("AUTO MANAGER PAUSING TORRENT: " + t->torrent_file().name()).c_str()); #endif - t->set_allow_peers(false); + // use graceful pause for auto-managed torrents + t->set_allow_peers(false, true); } } } @@ -2754,6 +2755,7 @@ namespace aux { if (!pi) continue; torrent* t = p->associated_torrent().lock().get(); if (!t) continue; + if (t->is_paused()) continue; if (pi->optimistically_unchoked) { @@ -2849,7 +2851,7 @@ namespace aux { 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 (p->ignore_unchoke_slots() || t == 0 || pi == 0 || t->is_paused()) continue; if (m_settings.choking_algorithm == session_settings::bittyrant_choker) { diff --git a/src/torrent.cpp b/src/torrent.cpp index 29d043558..f950b3cc8 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -389,6 +389,7 @@ namespace libtorrent , m_last_upload(0) , m_downloaders(0xffffff) , m_interface_index(0) + , m_graceful_pause_mode(false) { #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING (*m_ses.m_logger) << time_now_string() << " creating torrent: " @@ -3233,6 +3234,7 @@ namespace libtorrent { INVARIANT_CHECK; + TORRENT_ASSERT(!m_graceful_pause_mode); TORRENT_ASSERT(c.is_choked()); TORRENT_ASSERT(!c.ignore_unchoke_slots()); // when we're unchoking the optimistic slots, we might @@ -5008,7 +5010,7 @@ namespace libtorrent void torrent::check_invariant() const { TORRENT_ASSERT(m_ses.is_network_thread()); - if (is_paused()) TORRENT_ASSERT(num_peers() == 0); + if (is_paused()) TORRENT_ASSERT(num_peers() == 0 || m_graceful_pause_mode); if (!should_check_files()) TORRENT_ASSERT(m_state != torrent_status::checking_files); @@ -5526,7 +5528,8 @@ namespace libtorrent || m_state == torrent_status::queued_for_checking) && (m_allow_peers || m_auto_managed) && !has_error() - && !m_abort; + && !m_abort + && !m_graceful_pause_mode; } void torrent::flush_cache() @@ -5546,20 +5549,24 @@ namespace libtorrent bool torrent::is_paused() const { TORRENT_ASSERT(m_ses.is_network_thread()); - return !m_allow_peers || m_ses.is_paused(); + return !m_allow_peers || m_ses.is_paused() || m_graceful_pause_mode; } - void torrent::pause() + void torrent::pause(bool graceful) { TORRENT_ASSERT(m_ses.is_network_thread()); INVARIANT_CHECK; if (!m_allow_peers) return; bool checking_files = should_check_files(); - m_allow_peers = false; + if (!graceful) m_allow_peers = false; m_announce_to_dht = false; m_announce_to_trackers = false; m_announce_to_lsd = false; + + // mark torrent as upload only to make peers stop request more pieces + m_graceful_pause_mode = graceful; + if (!m_ses.is_paused()) do_pause(); if (checking_files && !should_check_files()) @@ -5609,7 +5616,44 @@ namespace libtorrent alerts().post_alert(torrent_paused_alert(get_handle())); } - disconnect_all(errors::torrent_paused); + if (!m_graceful_pause_mode) + { + disconnect_all(errors::torrent_paused); + } + else + { + // disconnect all peers with no outstanding data to receive + // and choke all remaining peers to prevent responding to new + // requests + for (std::set::iterator i = m_connections.begin() + , end(m_connections.end()); i != end;) + { + std::set::iterator j = i++; + peer_connection* p = *j; + TORRENT_ASSERT(p->associated_torrent().lock().get() == this); + + if (p->is_disconnecting()) + m_connections.erase(j); + + if (p->outstanding_bytes() > 0) + { +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING + (*p->m_logger) << "*** CHOKING PEER: torrent graceful paused\n"; +#endif + // remove any un-sent requests from the queue + p->clear_request_queue(); + // don't accept new requests from the peer + if (!p->is_choked()) m_ses.choke_peer(*p); + continue; + } + +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_ERROR_LOGGING + (*p->m_logger) << "*** CLOSING CONNECTION: torrent_paused\n"; +#endif + p->disconnect(errors::torrent_paused); + } + } + stop_announcing(); } @@ -5628,14 +5672,17 @@ namespace libtorrent } #endif - void torrent::set_allow_peers(bool b) + void torrent::set_allow_peers(bool b, bool graceful) { TORRENT_ASSERT(m_ses.is_network_thread()); - if (m_allow_peers == b) return; + + if (m_allow_peers == b + && m_graceful_pause_mode == graceful) return; bool checking_files = should_check_files(); m_allow_peers = b; + m_graceful_pause_mode = graceful; if (!b) { @@ -5667,6 +5714,7 @@ namespace libtorrent m_announce_to_dht = true; m_announce_to_trackers = true; m_announce_to_lsd = true; + m_graceful_pause_mode = false; do_resume(); if (!checking_files && should_check_files()) queue_torrent_check(); @@ -6706,7 +6754,7 @@ namespace libtorrent st.num_complete = (m_complete == 0xffffff) ? -1 : m_complete; st.num_incomplete = (m_incomplete == 0xffffff) ? -1 : m_incomplete; - st.paused = !m_allow_peers; + st.paused = !m_allow_peers || m_graceful_pause_mode; bytes_done(st, flags & torrent_handle::query_accurate_download_counters); TORRENT_ASSERT(st.total_wanted_done >= 0); TORRENT_ASSERT(st.total_done >= st.total_wanted_done); diff --git a/src/torrent_handle.cpp b/src/torrent_handle.cpp index 5474f99a1..8f78830f6 100644 --- a/src/torrent_handle.cpp +++ b/src/torrent_handle.cpp @@ -371,10 +371,10 @@ namespace libtorrent return r; } - void torrent_handle::pause() const + void torrent_handle::pause(int flags) const { INVARIANT_CHECK; - TORRENT_ASYNC_CALL(pause); + TORRENT_ASYNC_CALL1(pause, bool(flags & graceful_pause)); } void torrent_handle::set_share_mode(bool b) const