added upload mode

This commit is contained in:
Arvid Norberg 2009-06-18 22:32:55 +00:00
parent 6b048dc85e
commit f09774607f
12 changed files with 209 additions and 54 deletions

View File

@ -1,4 +1,5 @@
* by default, all piece priorities are set to 0 when a write fails. * introduced an upload mode, which torrents are switched into when
it hits a disk write error, instead of stopping the torrent.
this lets libtorrent keep uploading the parts it has when it this lets libtorrent keep uploading the parts it has when it
encounters a disk-full error for instance encounters a disk-full error for instance
* improved disk error handling and expanded use of error_code in * improved disk error handling and expanded use of error_code in

View File

@ -38,6 +38,34 @@ For a description on how to create torrent files, see make_torrent_.
.. _make_torrent: make_torrent.html .. _make_torrent: make_torrent.html
things to keep in mind
======================
A common problem developers are facing is torrents stopping without explanation.
Here is a description on which conditions libtorrent will stop your torrents,
how to find out about it and what to do about it.
Make sure to keep track of the paused state, the error state and the upload
mode of your torrents. By default, torrents are auto-managed, which means
libtorrent will pause them, unpause them, scrape them and take them out
of upload-mode automatically.
Whenever a torrent encounters a fatal error, it will be stopped, and the
``session_status::error`` will describe the error that caused it. If a torrent
is auto managed, it is scraped periodically and paused or resumed based on
the number of downloaders per seed. This will effectively seed torrents that
are in the greatest need of seeds.
If a torrent hits a disk write error, it will be put into upload mode. This
means it will not download anything, but only upload. The assumption is that
the write error is caused by a full disk or write permission errors. If the
torrent is auto-managed, it will periodically be taken out of the upload
mode, trying to write things to the disk again. This means torrent will recover
from certain disk errors if the problem is resolved. If the torrent is not
auto managed, you have to call `set_upload_mode()`_ to turn
downloading back on again.
network primitives network primitives
================== ==================
@ -306,6 +334,7 @@ add_torrent()
void* userdata; void* userdata;
bool seed_mode; bool seed_mode;
bool override_resume_data; bool override_resume_data;
bool upload_mode;
}; };
torrent_handle add_torrent(add_torrent_params const& params); torrent_handle add_torrent(add_torrent_params const& params);
@ -413,6 +442,11 @@ If ``override_resume_data`` is set to true, the ``paused`` and ``auto_managed``
state of the torrent are not loaded from the resume data, but the states requested state of the torrent are not loaded from the resume data, but the states requested
by this ``add_torrent_params`` will override it. by this ``add_torrent_params`` will override it.
If ``upload_mode`` is set to true, the torrent will be initialized in upload-mode,
which means it will not make any piece requests. This state is typically entered
on disk I/O errors, and if the torrent is also auto managed, it will be taken out
of this state periodically. This mode can be used to avoid race conditions when
adjusting priorities of pieces before allowing the torrent to start downloading.
remove_torrent() remove_torrent()
---------------- ----------------
@ -1841,6 +1875,7 @@ Its declaration looks like this::
bool is_seed() const; bool is_seed() const;
void force_recheck() const; void force_recheck() const;
void clear_error() const; void clear_error() const;
void set_upload_mode(bool m) const;
void resolve_countries(bool r); void resolve_countries(bool r);
bool resolve_countries() const; bool resolve_countries() const;
@ -2273,6 +2308,23 @@ clear_error()
If the torrent is in an error state (i.e. ``torrent_status::error`` is non-empty), this If the torrent is in an error state (i.e. ``torrent_status::error`` is non-empty), this
will clear the error and start the torrent again. will clear the error and start the torrent again.
set_upload_mode()
-----------------
::
void set_upload_mode(bool m) const;
Explicitly sets the upload mode of the torrent. In upload mode, the torrent will not
request any pieces. If the torrent is auto managed, it will automatically be taken out
of upload mode periodically (see ``session_settings::optimistic_disk_retry``). Torrents
are automatically put in upload mode whenever they encounter a disk write error.
``m`` should be true to enter upload mode, and false to leave it.
To test if a torrent is in upload mode, call ``torrent_handle::status()`` and inspect
``torrent_status::upload_mode``.
resolve_countries() resolve_countries()
------------------- -------------------
@ -2776,6 +2828,8 @@ It contains the following fields::
int sparse_regions; int sparse_regions;
bool seed_mode; bool seed_mode;
bool upload_mode;
}; };
``progress`` is a value in the range [0, 1], that represents the progress of the ``progress`` is a value in the range [0, 1], that represents the progress of the
@ -2974,6 +3028,13 @@ a limit on the number of sparse regions in a single file there.
started in seed mode, it will leave seed mode once all pieces have been started in seed mode, it will leave seed mode once all pieces have been
checked or as soon as one piece fails the hash check. checked or as soon as one piece fails the hash check.
``upload_mode`` is true if the torrent is blocked from downloading. This
typically happens when a disk write operation fails. If the torrent is
auto-managed, it will periodically be taken out of this state, in the
hope that the disk condition (be it disk full or permission errors) has
been resolved. If the torrent is not auto-managed, you have to explicitly
take it out of the upload mode by calling `set_upload_mode()`_ on the
torrent_handle_.
peer_info peer_info
========= =========
@ -3451,7 +3512,7 @@ session_settings
int read_cache_line_size; int read_cache_line_size;
int write_cache_line_size; int write_cache_line_size;
bool adjust_priority_on_disk_failure; int optimistic_disk_retry;
}; };
``user_agent`` this is the client identification to the tracker. ``user_agent`` this is the client identification to the tracker.
@ -3857,15 +3918,14 @@ When a piece in the write cache has ``write_cache_line_size`` contiguous
blocks in it, they will be flushed. Setting this to 1 effectively blocks in it, they will be flushed. Setting this to 1 effectively
disables the write cache. disables the write cache.
``adjust_priority_on_disk_failure`` specifies what libtorrent should do ``optimistic_disk_retry`` is the number of seconds from a disk write
on disk failures. If this is set to true, instead of pausing the torrent errors occur on a torrent until libtorrent will take it out of the
and setting it to an error state when it fails to write to disk, the upload mode, to test if the error condition has been fixed.
priorities of all pieces are set to 0. This effectively means the client
can keep seeding the parts that were already downloaded, instead of
leaving the swarm because of the error.
If a read operation fails, it will still set an error on the torrent libtorrent will only do this automatically for auto managed torrents.
and pause it.
You can explicitly take a torrent out of upload only mode using
`set_upload_mode()`_.
pe_settings pe_settings
=========== ===========

View File

@ -427,7 +427,13 @@ namespace libtorrent
void make_time_critical(piece_block const& block); void make_time_critical(piece_block const& block);
// adds a block to the request queue // adds a block to the request queue
void add_request(piece_block const& b, bool time_critical = false); // returns true if successful, false otherwise
bool add_request(piece_block const& b, bool time_critical = false);
// clears the request queue and sends cancels for all messages
// in the download queue
void cancel_all_requests();
// removes a block from the request queue or download queue // removes a block from the request queue or download queue
// sends a cancel message if appropriate // sends a cancel message if appropriate
// refills the request queue, and possibly ignoring pieces requested // refills the request queue, and possibly ignoring pieces requested

View File

@ -174,6 +174,7 @@ namespace libtorrent
, userdata(0) , userdata(0)
, seed_mode(false) , seed_mode(false)
, override_resume_data(false) , override_resume_data(false)
, upload_mode(false)
{} {}
boost::intrusive_ptr<torrent_info> ti; boost::intrusive_ptr<torrent_info> ti;
@ -190,6 +191,7 @@ namespace libtorrent
void* userdata; void* userdata;
bool seed_mode; bool seed_mode;
bool override_resume_data; bool override_resume_data;
bool upload_mode;
}; };
class TORRENT_EXPORT session: public boost::noncopyable, aux::eh_initializer class TORRENT_EXPORT session: public boost::noncopyable, aux::eh_initializer

View File

@ -173,7 +173,7 @@ namespace libtorrent
, disk_cache_algorithm(largest_contiguous) , disk_cache_algorithm(largest_contiguous)
, read_cache_line_size(16) , read_cache_line_size(16)
, write_cache_line_size(32) , write_cache_line_size(32)
, adjust_priority_on_disk_failure(true) , optimistic_disk_retry(10 * 60)
{} {}
// this is the user agent that will be sent to the tracker // this is the user agent that will be sent to the tracker
@ -600,13 +600,9 @@ namespace libtorrent
// is flushed immediately // is flushed immediately
int write_cache_line_size; int write_cache_line_size;
// if this is set to true, piece priorities // this is the number of seconds a disk failure
// will be automatically changed if there is // occurs until libtorrent will re-try.
// an error writing to the disk, attempting int optimistic_disk_retry;
// to avoid hitting the error again. If there
// is an error reading, the torrent will be
// paused
bool adjust_priority_on_disk_failure;
}; };
#ifndef TORRENT_DISABLE_DHT #ifndef TORRENT_DISABLE_DHT

View File

@ -159,6 +159,9 @@ namespace libtorrent
void start_announcing(); void start_announcing();
void stop_announcing(); void stop_announcing();
void set_upload_mode(bool b);
bool upload_mode() const { return m_upload_mode; }
int seed_rank(session_settings const& s) const; int seed_rank(session_settings const& s) const;
enum flags_t { overwrite_existing = 1 }; enum flags_t { overwrite_existing = 1 };
@ -730,6 +733,9 @@ namespace libtorrent
// one of the trackers in this torrent // one of the trackers in this torrent
ptime m_last_scrape; ptime m_last_scrape;
// the time when we switched to upload mode
ptime m_upload_mode_time;
boost::intrusive_ptr<torrent_info> m_torrent_file; boost::intrusive_ptr<torrent_info> m_torrent_file;
void parse_response(const entry& e, std::vector<peer_entry>& peer_list); void parse_response(const entry& e, std::vector<peer_entry>& peer_list);
@ -995,6 +1001,9 @@ namespace libtorrent
// is true if this torrent has been paused // is true if this torrent has been paused
bool m_paused:1; bool m_paused:1;
// set to true when this torrent may not download anything
bool m_upload_mode:1;
// if this is true, libtorrent may pause and resume // if this is true, libtorrent may pause and resume
// this torrent depending on queuing rules. Torrents // this torrent depending on queuing rules. Torrents
// started with auto_managed flag set may be added in // started with auto_managed flag set may be added in

View File

@ -273,6 +273,11 @@ namespace libtorrent
// is true if this torrent is (still) in seed_mode // is true if this torrent is (still) in seed_mode
bool seed_mode; bool seed_mode;
// this is set to true when the torrent is blocked
// from downloading, typically caused by a file
// write operation failing
bool upload_mode;
}; };
struct TORRENT_EXPORT block_info struct TORRENT_EXPORT block_info
@ -397,6 +402,8 @@ namespace libtorrent
bool is_paused() const; bool is_paused() const;
void pause() const; void pause() const;
void resume() const; void resume() const;
void set_upload_mode(bool b) const;
void force_recheck() const; void force_recheck() const;
void save_resume_data() const; void save_resume_data() const;

View File

@ -2374,7 +2374,7 @@ namespace libtorrent
++m_queued_time_critical; ++m_queued_time_critical;
} }
void peer_connection::add_request(piece_block const& block, bool time_critical) bool peer_connection::add_request(piece_block const& block, bool time_critical)
{ {
// INVARIANT_CHECK; // INVARIANT_CHECK;
@ -2393,6 +2393,8 @@ namespace libtorrent
TORRENT_ASSERT(std::find(m_request_queue.begin(), m_request_queue.end() TORRENT_ASSERT(std::find(m_request_queue.begin(), m_request_queue.end()
, block) == m_request_queue.end()); , block) == m_request_queue.end());
if (t->upload_mode()) return false;
piece_picker::piece_state_t state; piece_picker::piece_state_t state;
peer_speed_t speed = peer_speed(); peer_speed_t speed = peer_speed();
char const* speedmsg = 0; char const* speedmsg = 0;
@ -2413,7 +2415,7 @@ namespace libtorrent
} }
if (!t->picker().mark_as_downloading(block, peer_info_struct(), state)) if (!t->picker().mark_as_downloading(block, peer_info_struct(), state))
return; return false;
if (t->alerts().should_post<block_downloading_alert>()) if (t->alerts().should_post<block_downloading_alert>())
{ {
@ -2431,6 +2433,53 @@ namespace libtorrent
{ {
m_request_queue.push_back(block); m_request_queue.push_back(block);
} }
return true;
}
void peer_connection::cancel_all_requests()
{
INVARIANT_CHECK;
boost::shared_ptr<torrent> t = m_torrent.lock();
// this peer might be disconnecting
if (!t) return;
TORRENT_ASSERT(t->valid_metadata());
#ifdef TORRENT_VERBOSE_LOGGING
(*m_logger) << time_now_string() << " *** CANCEL ALL REQUESTS\n";
#endif
while (!m_request_queue.empty())
{
t->picker().abort_download(m_request_queue.back());
m_request_queue.pop_back();
}
for (std::vector<pending_block>::iterator i = m_download_queue.begin()
, end(m_download_queue.end()); i != end; ++i)
{
piece_block b = i->block;
int block_offset = b.block_index * t->block_size();
int block_size
= (std::min)(t->torrent_file().piece_size(b.piece_index)-block_offset,
t->block_size());
TORRENT_ASSERT(block_size > 0);
TORRENT_ASSERT(block_size <= t->block_size());
peer_request r;
r.piece = b.piece_index;
r.start = block_offset;
r.length = block_size;
#ifdef TORRENT_VERBOSE_LOGGING
(*m_logger) << time_now_string()
<< " ==> CANCEL [ piece: " << b.piece_index << " | s: "
<< block_offset << " | l: " << block_size << " | " << b.block_index << " ]\n";
#endif
write_cancel(r);
}
} }
void peer_connection::cancel_request(piece_block const& block) void peer_connection::cancel_request(piece_block const& block)
@ -2588,7 +2637,8 @@ namespace libtorrent
boost::shared_ptr<torrent> t = m_torrent.lock(); boost::shared_ptr<torrent> t = m_torrent.lock();
TORRENT_ASSERT(t); TORRENT_ASSERT(t);
if ((int)m_download_queue.size() >= m_desired_queue_size) return; if ((int)m_download_queue.size() >= m_desired_queue_size
|| t->upload_mode()) return;
bool empty_download_queue = m_download_queue.empty(); bool empty_download_queue = m_download_queue.empty();

View File

@ -193,6 +193,7 @@ namespace libtorrent
{ {
if (t.is_seed()) return; if (t.is_seed()) return;
if (c.no_download()) return; if (c.no_download()) return;
if (t.upload_mode()) return;
TORRENT_ASSERT(t.valid_metadata()); TORRENT_ASSERT(t.valid_metadata());
TORRENT_ASSERT(c.peer_info_struct() != 0 || !dynamic_cast<bt_peer_connection*>(&c)); TORRENT_ASSERT(c.peer_info_struct() != 0 || !dynamic_cast<bt_peer_connection*>(&c));
@ -313,7 +314,7 @@ namespace libtorrent
// ok, we found a piece that's not being downloaded // ok, we found a piece that's not being downloaded
// by somebody else. request it from this peer // by somebody else. request it from this peer
// and return // and return
c.add_request(*i); if (!c.add_request(*i)) continue;
TORRENT_ASSERT(p.num_peers(*i) == 1); TORRENT_ASSERT(p.num_peers(*i) == 1);
TORRENT_ASSERT(p.is_requested(*i)); TORRENT_ASSERT(p.is_requested(*i));
num_requests--; num_requests--;

View File

@ -144,6 +144,7 @@ namespace libtorrent
, m_total_downloaded(0) , m_total_downloaded(0)
, m_started(time_now()) , m_started(time_now())
, m_last_scrape(min_time()) , m_last_scrape(min_time())
, m_upload_mode_time(time_now())
, m_torrent_file(p.ti ? p.ti : new torrent_info(p.info_hash)) , m_torrent_file(p.ti ? p.ti : new torrent_info(p.info_hash))
, m_storage(0) , m_storage(0)
, m_host_resolver(ses.m_io_service) , m_host_resolver(ses.m_io_service)
@ -180,6 +181,7 @@ namespace libtorrent
, m_time_scaler(0) , m_time_scaler(0)
, m_abort(false) , m_abort(false)
, m_paused(p.paused) , m_paused(p.paused)
, m_upload_mode(p.upload_mode)
, m_auto_managed(p.auto_managed) , m_auto_managed(p.auto_managed)
#ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES
, m_resolving_country(false) , m_resolving_country(false)
@ -341,6 +343,36 @@ namespace libtorrent
} }
} }
void torrent::set_upload_mode(bool b)
{
if (b == m_upload_mode) return;
m_upload_mode = b;
if (m_upload_mode)
{
// clear request queues of all peers
for (std::set<peer_connection*>::iterator i = m_connections.begin()
, end(m_connections.end()); i != end; ++i)
{
peer_connection* p = (*i);
p->cancel_all_requests();
}
// this is used to try leaving upload only mode periodically
m_upload_mode_time = time_now();
}
else
{
// send_block_requests on all peers
for (std::set<peer_connection*>::iterator i = m_connections.begin()
, end(m_connections.end()); i != end; ++i)
{
peer_connection* p = (*i);
p->send_block_requests();
}
}
}
void torrent::handle_disk_error(disk_io_job const& j, peer_connection* c) void torrent::handle_disk_error(disk_io_job const& j, peer_connection* c)
{ {
if (!j.error) return; if (!j.error) return;
@ -389,20 +421,8 @@ namespace libtorrent
// and the filesystem doesn't support sparse files, only zero the priorities // and the filesystem doesn't support sparse files, only zero the priorities
// of the pieces that are at the tails of all files, leaving everything // of the pieces that are at the tails of all files, leaving everything
// up to the highest written piece in each file // up to the highest written piece in each file
if (m_ses.settings().adjust_priority_on_disk_failure set_upload_mode(true);
&& has_picker()) return;
{
bool filter_updated = false;
bool was_finished = is_finished();
const int num_pieces = m_torrent_file->num_pieces();
for (int i = 0; i < num_pieces; ++i)
{
filter_updated |= m_picker->set_piece_priority(i, 0);
TORRENT_ASSERT(num_have() >= m_picker->num_have_filtered());
}
if (filter_updated) update_peer_interest(was_finished);
return;
}
} }
// put the torrent in an error-state // put the torrent in an error-state
@ -4897,6 +4917,8 @@ namespace libtorrent
{ {
INVARIANT_CHECK; INVARIANT_CHECK;
ptime now = time_now();
#ifndef TORRENT_DISABLE_EXTENSIONS #ifndef TORRENT_DISABLE_EXTENSIONS
for (extension_list_t::iterator i = m_extensions.begin() for (extension_list_t::iterator i = m_extensions.begin()
, end(m_extensions.end()); i != end; ++i) , end(m_extensions.end()); i != end; ++i)
@ -4932,6 +4954,15 @@ namespace libtorrent
m_policy.pulse(); m_policy.pulse();
} }
// if we're in upload only mode and we're auto-managed
// leave upload mode every 10 minutes hoping that the error
// condition has been fixed
if (m_upload_mode && m_auto_managed && now - m_upload_mode_time
> seconds(m_settings.optimistic_disk_retry))
{
set_upload_mode(false);
}
if (is_paused()) if (is_paused())
{ {
// let the stats fade out to 0 // let the stats fade out to 0
@ -4965,8 +4996,6 @@ namespace libtorrent
if (is_seed()) m_seeding_time += since_last_tick; if (is_seed()) m_seeding_time += since_last_tick;
m_active_time += since_last_tick; m_active_time += since_last_tick;
ptime now = time_now();
// ---- TIME CRITICAL PIECES ---- // ---- TIME CRITICAL PIECES ----
if (!m_time_critical_pieces.empty()) if (!m_time_critical_pieces.empty())
@ -5437,6 +5466,7 @@ namespace libtorrent
{ {
st.last_scrape = total_seconds(now - m_last_scrape); st.last_scrape = total_seconds(now - m_last_scrape);
} }
st.upload_mode = m_upload_mode;
st.up_bandwidth_queue = 0; st.up_bandwidth_queue = 0;
st.down_bandwidth_queue = 0; st.down_bandwidth_queue = 0;

View File

@ -290,6 +290,12 @@ namespace libtorrent
TORRENT_FORWARD(pause()); TORRENT_FORWARD(pause());
} }
void torrent_handle::set_upload_mode(bool b) const
{
INVARIANT_CHECK;
TORRENT_FORWARD(set_upload_mode(b));
}
void torrent_handle::save_resume_data() const void torrent_handle::save_resume_data() const
{ {
INVARIANT_CHECK; INVARIANT_CHECK;

View File

@ -271,24 +271,11 @@ void test_transfer(bool test_disk_full = false)
std::cerr << "moving storage" << std::endl; std::cerr << "moving storage" << std::endl;
} }
if (test_disk_full && tor2.is_finished()) if (test_disk_full && st2.upload_mode)
{ {
test_disk_full = false; test_disk_full = false;
std::vector<int> priorities2 = tor2.piece_priorities();
std::cerr << "piece priorities: ";
for (std::vector<int>::iterator i = priorities2.begin()
, end(priorities2.end()); i != end; ++i)
{
TEST_CHECK(*i == 0);
std::cerr << *i << ", ";
}
std::cerr << std::endl;
((test_storage*)tor2.get_storage_impl())->m_limit = 16 * 1024 * 1024; ((test_storage*)tor2.get_storage_impl())->m_limit = 16 * 1024 * 1024;
tor2.prioritize_pieces(priorities); tor2.set_upload_mode(false);
std::cerr << "setting priorities: ";
std::copy(priorities.begin(), priorities.end(), std::ostream_iterator<int>(std::cerr, ", "));
std::cerr << std::endl;
continue; continue;
} }