From 5e87420b6f5e1e1c3734c7a00f7a4dede8403f08 Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Wed, 10 Jun 2009 09:20:55 +0000 Subject: [PATCH] set all piece priorities to 0 when a write fails --- ChangeLog | 3 + docs/manual.rst | 12 ++ include/libtorrent/session_settings.hpp | 9 ++ src/peer_connection.cpp | 2 + src/torrent.cpp | 26 ++++- test/test_transfer.cpp | 139 +++++++++++++++++++++++- 6 files changed, 185 insertions(+), 6 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4684525a7..d3b54f9ae 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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 diff --git a/docs/manual.rst b/docs/manual.rst index 0dee5a47e..d984a2e3a 100644 --- a/docs/manual.rst +++ b/docs/manual.rst @@ -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 =========== diff --git a/include/libtorrent/session_settings.hpp b/include/libtorrent/session_settings.hpp index 775f297b8..f0c8e584b 100644 --- a/include/libtorrent/session_settings.hpp +++ b/include/libtorrent/session_settings.hpp @@ -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 diff --git a/src/peer_connection.cpp b/src/peer_connection.cpp index 3c77058cd..660849be1 100644 --- a/src/peer_connection.cpp +++ b/src/peer_connection.cpp @@ -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 diff --git a/src/torrent.cpp b/src/torrent.cpp index 3425beda4..b5f821331 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -376,11 +376,35 @@ namespace libtorrent return; } - // notify the user of the error if (alerts().should_post()) 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(); diff --git a/test/test_transfer.cpp b/test/test_transfer.cpp index 7cc387316..bc77cc95c 100644 --- a/test/test_transfer.cpp +++ b/test/test_transfer.cpp @@ -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 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 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 priorities2 = tor2.piece_priorities(); + std::cerr << "piece priorities: "; + for (std::vector::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(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();