forked from premiere/premiere-libtorrent
set all piece priorities to 0 when a write fails
This commit is contained in:
parent
6b908e5f3f
commit
5e87420b6f
|
@ -1,3 +1,6 @@
|
|||
* by default, all piece priorities are set to 0 when a write fails.
|
||||
this lets libtorrent keep uploading the parts it has when it
|
||||
encounters a disk-full error for instance
|
||||
* improved disk error handling and expanded use of error_code in
|
||||
error reporting. added a bandwidth state, bw_disk, when waiting
|
||||
for the disk io thread to catch up writing buffers
|
||||
|
|
|
@ -3450,6 +3450,8 @@ session_settings
|
|||
|
||||
int read_cache_line_size;
|
||||
int write_cache_line_size;
|
||||
|
||||
bool adjust_priority_on_disk_failure;
|
||||
};
|
||||
|
||||
``user_agent`` this is the client identification to the tracker.
|
||||
|
@ -3855,6 +3857,16 @@ 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
|
||||
disables the write cache.
|
||||
|
||||
``adjust_priority_on_disk_failure`` specifies what libtorrent should do
|
||||
on disk failures. If this is set to true, instead of pausing the torrent
|
||||
and setting it to an error state when it fails to write to disk, the
|
||||
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
|
||||
and pause it.
|
||||
|
||||
pe_settings
|
||||
===========
|
||||
|
||||
|
|
|
@ -173,6 +173,7 @@ namespace libtorrent
|
|||
, disk_cache_algorithm(largest_contiguous)
|
||||
, read_cache_line_size(16)
|
||||
, write_cache_line_size(32)
|
||||
, adjust_priority_on_disk_failure(true)
|
||||
{}
|
||||
|
||||
// this is the user agent that will be sent to the tracker
|
||||
|
@ -598,6 +599,14 @@ namespace libtorrent
|
|||
// blocks is found in the write cache, it
|
||||
// is flushed immediately
|
||||
int write_cache_line_size;
|
||||
|
||||
// if this is set to true, piece priorities
|
||||
// will be automatically changed if there is
|
||||
// an error writing to the disk, attempting
|
||||
// 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
|
||||
|
|
|
@ -3652,6 +3652,7 @@ namespace libtorrent
|
|||
" ignore: " << (m_ignore_bandwidth_limits?"yes":"no") <<
|
||||
" buf: " << m_send_buffer.size() <<
|
||||
" connecting: " << (m_connecting?"yes":"no") <<
|
||||
" disconnecting: " << (m_disconnecting?"yes":"no") <<
|
||||
" ]\n";
|
||||
}
|
||||
else
|
||||
|
@ -3661,6 +3662,7 @@ namespace libtorrent
|
|||
" ignore: " << (m_ignore_bandwidth_limits?"yes":"no") <<
|
||||
" buf: " << m_send_buffer.size() <<
|
||||
" connecting: " << (m_connecting?"yes":"no") <<
|
||||
" disconnecting: " << (m_disconnecting?"yes":"no") <<
|
||||
" ]\n";
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -376,11 +376,35 @@ namespace libtorrent
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
// notify the user of the error
|
||||
if (alerts().should_post<file_error_alert>())
|
||||
alerts().post_alert(file_error_alert(j.error_file, get_handle(), j.error));
|
||||
|
||||
if (j.action == disk_io_job::write)
|
||||
{
|
||||
// if we failed to write, stop downloading and just
|
||||
// keep seeding.
|
||||
// TODO: make this depend on the error and on the filesystem the
|
||||
// files are being downloaded to. If the error is no_space_left_on_device
|
||||
// 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
|
||||
// up to the highest written piece in each file
|
||||
if (m_ses.settings().adjust_priority_on_disk_failure
|
||||
&& has_picker())
|
||||
{
|
||||
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
|
||||
set_error(j.error, j.error_file);
|
||||
pause();
|
||||
|
|
|
@ -105,7 +105,93 @@ void print_alert(alert const& a)
|
|||
std::cout << "ses1 (alert dispatch function): " << a.message() << std::endl;
|
||||
}
|
||||
|
||||
void test_transfer()
|
||||
// simulate a full disk
|
||||
struct test_storage : storage_interface
|
||||
{
|
||||
test_storage(file_storage const& fs, fs::path const& p, file_pool& fp)
|
||||
: m_lower_layer(default_storage_constructor(fs, p, fp))
|
||||
, m_written(0)
|
||||
, m_limit(16 * 1024 * 2)
|
||||
{}
|
||||
|
||||
virtual bool initialize(bool allocate_files)
|
||||
{ return m_lower_layer->initialize(allocate_files); }
|
||||
|
||||
virtual bool has_any_file()
|
||||
{ return m_lower_layer->has_any_file(); }
|
||||
|
||||
virtual int readv(file::iovec_t const* bufs, int slot, int offset, int num_bufs)
|
||||
{ return m_lower_layer->readv(bufs, slot, offset, num_bufs); }
|
||||
|
||||
virtual int writev(file::iovec_t const* bufs, int slot, int offset, int num_bufs)
|
||||
{
|
||||
int ret = m_lower_layer->writev(bufs, slot, offset, num_bufs);
|
||||
if (ret > 0) m_written += ret;
|
||||
if (m_written > m_limit)
|
||||
{
|
||||
set_error("", error_code(boost::system::errc::no_space_on_device, get_posix_category()));
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
virtual int read(char* buf, int slot, int offset, int size)
|
||||
{ return m_lower_layer->read(buf, slot, offset, size); }
|
||||
|
||||
virtual int write(const char* buf, int slot, int offset, int size)
|
||||
{
|
||||
int ret = m_lower_layer->write(buf, slot, offset, size);
|
||||
if (ret > 0) m_written += ret;
|
||||
if (m_written > m_limit)
|
||||
{
|
||||
set_error("", error_code(boost::system::errc::no_space_on_device, get_posix_category()));
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
virtual int sparse_end(int start) const
|
||||
{ return m_lower_layer->sparse_end(start); }
|
||||
|
||||
virtual bool move_storage(fs::path save_path)
|
||||
{ return m_lower_layer->move_storage(save_path); }
|
||||
|
||||
virtual bool verify_resume_data(lazy_entry const& rd, std::string& error)
|
||||
{ return m_lower_layer->verify_resume_data(rd, error); }
|
||||
|
||||
virtual bool write_resume_data(entry& rd) const
|
||||
{ return m_lower_layer->write_resume_data(rd); }
|
||||
|
||||
virtual bool move_slot(int src_slot, int dst_slot)
|
||||
{ return m_lower_layer->move_slot(src_slot, dst_slot); }
|
||||
|
||||
virtual bool swap_slots(int slot1, int slot2)
|
||||
{ return m_lower_layer->swap_slots(slot1, slot2); }
|
||||
|
||||
virtual bool swap_slots3(int slot1, int slot2, int slot3)
|
||||
{ return m_lower_layer->swap_slots3(slot1, slot2, slot3); }
|
||||
|
||||
virtual bool release_files() { return m_lower_layer->release_files(); }
|
||||
|
||||
virtual bool rename_file(int index, std::string const& new_filename)
|
||||
{ return m_lower_layer->rename_file(index, new_filename); }
|
||||
|
||||
virtual bool delete_files() { return m_lower_layer->delete_files(); }
|
||||
|
||||
virtual ~test_storage() {}
|
||||
|
||||
boost::scoped_ptr<storage_interface> m_lower_layer;
|
||||
int m_written;
|
||||
int m_limit;
|
||||
};
|
||||
|
||||
storage_interface* test_storage_constructor(file_storage const& fs
|
||||
, fs::path const& path, file_pool& fp)
|
||||
{
|
||||
return new test_storage(fs, path, fp);
|
||||
}
|
||||
|
||||
void test_transfer(bool test_disk_full = false)
|
||||
{
|
||||
session ses1(fingerprint("LT", 0, 1, 0, 0), std::make_pair(48075, 49000), "0.0.0.0", 0);
|
||||
session ses2(fingerprint("LT", 0, 1, 0, 0), std::make_pair(49075, 50000), "0.0.0.0", 0);
|
||||
|
@ -126,9 +212,16 @@ void test_transfer()
|
|||
boost::intrusive_ptr<torrent_info> t = ::create_torrent(&file, 16 * 1024);
|
||||
file.close();
|
||||
|
||||
add_torrent_params addp(&test_storage_constructor);
|
||||
|
||||
// test using piece sizes smaller than 16kB
|
||||
boost::tie(tor1, tor2, ignore) = setup_transfer(&ses1, &ses2, 0
|
||||
, true, false, true, "_transfer", 8 * 1024, &t);
|
||||
, true, false, true, "_transfer", 8 * 1024, &t, false, test_disk_full?&addp:0);
|
||||
|
||||
session_settings settings = ses1.settings();
|
||||
settings.min_reconnect_time = 1;
|
||||
ses1.set_settings(settings);
|
||||
ses2.set_settings(settings);
|
||||
|
||||
// set half of the pieces to priority 0
|
||||
int num_pieces = tor2.get_torrent_info().num_pieces();
|
||||
|
@ -167,6 +260,7 @@ void test_transfer()
|
|||
<< "\033[31m" << int(st2.upload_payload_rate / 1000.f) << "kB/s "
|
||||
<< "\033[0m" << int(st2.progress * 100) << "% "
|
||||
<< st2.num_peers
|
||||
<< " cc: " << st2.connect_candidates
|
||||
<< std::endl;
|
||||
|
||||
if (!test_move_storage && st2.progress > 0.25f)
|
||||
|
@ -177,18 +271,43 @@ void test_transfer()
|
|||
std::cerr << "moving storage" << std::endl;
|
||||
}
|
||||
|
||||
if (tor2.is_finished()) break;
|
||||
if (test_disk_full && tor2.is_finished())
|
||||
{
|
||||
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;
|
||||
tor2.prioritize_pieces(priorities);
|
||||
std::cerr << "setting priorities: ";
|
||||
std::copy(priorities.begin(), priorities.end(), std::ostream_iterator<int>(std::cerr, ", "));
|
||||
std::cerr << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!test_disk_full && tor2.is_finished()) break;
|
||||
|
||||
TEST_CHECK(st1.state == torrent_status::seeding
|
||||
|| st1.state == torrent_status::checking_files);
|
||||
TEST_CHECK(st2.state == torrent_status::downloading);
|
||||
TEST_CHECK(st2.state == torrent_status::downloading
|
||||
|| (test_disk_full && !st2.error.empty()));
|
||||
|
||||
test_sleep(1000);
|
||||
}
|
||||
|
||||
TEST_CHECK(!tor2.is_seed());
|
||||
std::cerr << "torrent is finished (50% complete)" << std::endl;
|
||||
TEST_CHECK(tor2.is_finished());
|
||||
if (tor2.is_finished())
|
||||
std::cerr << "torrent is finished (50% complete)" << std::endl;
|
||||
|
||||
std::cerr << "force recheck" << std::endl;
|
||||
tor2.force_recheck();
|
||||
|
||||
for (int i = 0; i < 10; ++i)
|
||||
|
@ -298,6 +417,7 @@ void test_transfer()
|
|||
<< "\033[31m" << int(st2.upload_payload_rate / 1000.f) << "kB/s "
|
||||
<< "\033[0m" << int(st2.progress * 100) << "% "
|
||||
<< st2.num_peers
|
||||
<< " cc: " << st2.connect_candidates
|
||||
<< std::endl;
|
||||
|
||||
if (tor2.is_finished()) break;
|
||||
|
@ -322,6 +442,15 @@ int test_main()
|
|||
try { remove_all("./tmp1_transfer_moved"); } catch (std::exception&) {}
|
||||
try { remove_all("./tmp2_transfer_moved"); } catch (std::exception&) {}
|
||||
|
||||
// test with a (simulated) full disk
|
||||
test_transfer(true);
|
||||
return 0;
|
||||
|
||||
try { remove_all("./tmp1_transfer"); } catch (std::exception&) {}
|
||||
try { remove_all("./tmp2_transfer"); } catch (std::exception&) {}
|
||||
try { remove_all("./tmp1_transfer_moved"); } catch (std::exception&) {}
|
||||
try { remove_all("./tmp2_transfer_moved"); } catch (std::exception&) {}
|
||||
|
||||
#ifdef NDEBUG
|
||||
// test rate only makes sense in release mode
|
||||
test_rate();
|
||||
|
|
Loading…
Reference in New Issue