complete the error handling test and make it part of the default simulation test suite. It will run a file transfer between two clients repeatedly, each time cause another memory allocation fail, until every single memory allocation has failed once. Any invariant check failure, assertion or signal will cause the test to fail

This commit is contained in:
arvidn 2017-12-28 21:41:46 +01:00 committed by Arvid Norberg
parent 97ceeab9e3
commit 3a9861e237
17 changed files with 144 additions and 90 deletions

View File

@ -519,12 +519,12 @@ variant test_release : release
variant test_debug : debug
: <logging>on
<invariant-checks>full <boost-link>shared
<export-extra>on <debug-iterators>on <threading>multi <asserts>on
<export-extra>on <threading>multi <asserts>on
;
variant test_barebones : debug
: <ipv6>off <dht>off <extensions>off <logging>off <boost-link>shared
<deprecated-functions>off <invariant-checks>off
<export-extra>on <debug-iterators>on <threading>multi <asserts>on
<export-extra>on <threading>multi <asserts>on
;
variant test_arm : debug
: <ipv6>off <dht>off <extensions>off <logging>off

View File

@ -88,7 +88,7 @@ build_script:
# simulations
- cd %ROOT_DIRECTORY%\simulation
- if defined sim (
b2.exe --hash openssl-version=pre1.1 warnings-as-errors=on -j2 %compiler% address-model=%model% picker-debugging=on invariant-checks=full variant=%variant% deprecated-functions=off %linkflags% %include% link=shared crypto=built-in testing.execute=off
b2.exe --hash openssl-version=pre1.1 warnings-as-errors=on -j2 %compiler% address-model=%model% debug-iterators=off picker-debugging=on invariant-checks=full test_debug %linkflags% %include% boost-link=default link=static crypto=built-in define=BOOST_ASIO_DISABLE_IOCP testing.execute=off
)
test_script:
@ -107,7 +107,9 @@ test_script:
# specifiers when debug iterators are enabled. Specifically, constructors that
# allocate memory are still marked as noexcept. That results in program
# termination
# the IOCP backend in asio appears to have an issue where it hangs under
# certain unexpected terminations (through exceptions)
- cd %ROOT_DIRECTORY%\simulation
- if defined sim (
b2.exe --hash openssl-version=pre1.1 warnings-as-errors=on -j2 %compiler% address-model=%model% debug-iterators=off picker-debugging=on invariant-checks=full test_debug %linkflags% %include% boost-link=default link=static crypto=built-in
b2.exe --hash openssl-version=pre1.1 warnings-as-errors=on -j2 %compiler% address-model=%model% debug-iterators=off picker-debugging=on invariant-checks=full test_debug %linkflags% %include% boost-link=default link=static crypto=built-in define=BOOST_ASIO_DISABLE_IOCP
)

View File

@ -1261,7 +1261,7 @@ namespace aux {
#ifndef TORRENT_DISABLE_LOGGING
bool should_log() const override;
void session_log(char const* fmt, ...) const override TORRENT_FORMAT(2,3);
void session_log(char const* fmt, ...) const noexcept override TORRENT_FORMAT(2,3);
#endif
#ifndef TORRENT_DISABLE_EXTENSIONS
@ -1314,7 +1314,7 @@ namespace aux {
, error_code const& ec, const std::string& str
, seconds32 retry_interval) override;
bool should_log() const override;
void debug_log(const char* fmt, ...) const override TORRENT_FORMAT(2,3);
void debug_log(const char* fmt, ...) const noexcept override TORRENT_FORMAT(2,3);
session_interface& m_ses;
private:
// explicitly disallow assignment, to silence msvc warning

View File

@ -286,7 +286,9 @@ namespace aux {
, buffer_allocator_interface
{
disk_io_thread(io_service& ios, counters& cnt);
#if TORRENT_USE_ASSERTS
~disk_io_thread();
#endif
void set_settings(settings_pack const* sett);

View File

@ -160,6 +160,16 @@ namespace libtorrent {
// can handle common cases of packet size by 3 pools
struct TORRENT_EXTRA_EXPORT packet_pool : private single_threaded
{
// there's a bug in GCC where allocating these in
// member initializer expressions won't propagate exceptions.
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80683
packet_pool()
: m_syn_slab(TORRENT_UTP_HEADER)
, m_mtu_floor_slab(mtu_floor_size)
, m_mtu_ceiling_slab(mtu_ceiling_size)
{}
packet_pool(packet_pool&&) = default;
packet_ptr acquire(int const allocate)
{
TORRENT_ASSERT(is_single_thread());
@ -202,9 +212,9 @@ namespace libtorrent {
}
static int const mtu_floor_size = TORRENT_INET_MIN_MTU - TORRENT_IPV4_HEADER - TORRENT_UDP_HEADER;
static int const mtu_ceiling_size = TORRENT_ETHERNET_MTU - TORRENT_IPV4_HEADER - TORRENT_UDP_HEADER;
packet_slab m_syn_slab{ TORRENT_UTP_HEADER };
packet_slab m_mtu_floor_slab{ mtu_floor_size };
packet_slab m_mtu_ceiling_slab{ mtu_ceiling_size };
packet_slab m_syn_slab;
packet_slab m_mtu_floor_slab;
packet_slab m_mtu_ceiling_slab;
};
}

View File

@ -526,9 +526,9 @@ namespace aux {
#ifndef TORRENT_DISABLE_LOGGING
bool should_log(peer_log_alert::direction_t direction) const override;
void peer_log(peer_log_alert::direction_t direction
, char const* event, char const* fmt, ...) const override TORRENT_FORMAT(4,5);
, char const* event, char const* fmt, ...) const noexcept override TORRENT_FORMAT(4,5);
void peer_log(peer_log_alert::direction_t direction
, char const* event) const;
, char const* event) const noexcept;
time_point m_connect_time;
time_point m_bitfield_time;

View File

@ -65,7 +65,7 @@ namespace libtorrent {
#ifndef TORRENT_DISABLE_LOGGING
virtual bool should_log(peer_log_alert::direction_t direction) const = 0;
virtual void peer_log(peer_log_alert::direction_t direction
, char const* event, char const* fmt = "", ...) const TORRENT_FORMAT(4,5) = 0;
, char const* event, char const* fmt = "", ...) const noexcept TORRENT_FORMAT(4,5) = 0;
#endif
protected:
~peer_connection_interface() {}

View File

@ -1023,7 +1023,7 @@ namespace libtorrent {
// LOGGING
#ifndef TORRENT_DISABLE_LOGGING
bool should_log() const override;
void debug_log(const char* fmt, ...) const override TORRENT_FORMAT(2,3);
void debug_log(const char* fmt, ...) const noexcept override TORRENT_FORMAT(2,3);
void log_to_all_peers(char const* message);
time_point m_dht_start_time;

View File

@ -249,7 +249,7 @@ namespace libtorrent {
#ifndef TORRENT_DISABLE_LOGGING
virtual bool should_log() const = 0;
virtual void debug_log(const char* fmt, ...) const TORRENT_FORMAT(2,3) = 0;
virtual void debug_log(const char* fmt, ...) const noexcept TORRENT_FORMAT(2,3) = 0;
#endif
};

View File

@ -51,8 +51,6 @@ alias libtorrent-sims :
[ run test_fast_extensions.cpp ]
[ run test_file_pool.cpp ]
[ run test_save_resume.cpp ]
[ run test_error_handling.cpp ]
;
run test_error_handling.cpp ;
explicit test_error_handling ;

View File

@ -51,6 +51,10 @@ POSSIBILITY OF SUCH DAMAGE.
using namespace sim;
#if defined _MSC_VER && _ITERATOR_DEBUG_LEVEL > 0
// https://developercommunity.visualstudio.com/content/problem/140200/c-stl-stdvector-constructor-declared-with-noexcept.html
#error "msvc's standard library does not support bad_alloc with debug iterators. This test only works with debug iterators disabled on msvc. _ITERATOR_DEBUG_LEVEL=0"
#endif
std::string make_ep_string(char const* address, bool const is_v6
, char const* port)
@ -105,7 +109,7 @@ void run_test(HandleAlerts const& on_alert, Test const& test)
// only monitor alerts for session 0 (the downloader)
print_alerts(*ses[0], [=](lt::session& ses, lt::alert const* a) {
if (auto ta = alert_cast<lt::torrent_added_alert>(a))
if (auto ta = alert_cast<lt::add_torrent_alert>(a))
{
ta->handle.connect_peer(lt::tcp::endpoint(peer1, 6881));
}
@ -149,6 +153,22 @@ void* operator new(std::size_t sz)
{
char stack[10000];
print_backtrace(stack, sizeof(stack), 40, nullptr);
#ifdef _MSC_VER
// this is a bit unfortunate. Some MSVC standard containers really don't move
// with noexcept, by actually allocating memory (i.e. it's not just a matter
// of accidentally missing noexcept specifiers). The heterogeneous queue used
// by the alert manager requires alerts to be noexcept move constructable and
// move assignable, which they claim to be, even though on MSVC some of them
// aren't. Things will improve in C++17 and it doesn't seem worth the trouble
// to make the heterogeneous queue support throwing moves, nor to replace all
// standard types with variants that can move noexcept.
if (std::strstr(stack, " libtorrent::entry::operator= ") != nullptr
|| std::strstr(stack, " libtorrent::aux::noexcept_movable<std::map<") != nullptr)
{
++g_alloc_counter;
return std::malloc(sz);
}
#endif
std::printf("\n\nthrowing bad_alloc (as part of test)\n%s\n\n\n", stack);
throw std::bad_alloc();
}
@ -162,7 +182,7 @@ void operator delete(void* ptr) noexcept
TORRENT_TEST(error_handling)
{
for (int i = 0; i < 3000; ++i)
for (int i = 0; i < 8000; ++i)
{
// this will clear the history of all output we've printed so far.
// if we encounter an error from now on, we'll only print the relevant
@ -176,7 +196,7 @@ TORRENT_TEST(error_handling)
std::printf("\n\n === ROUND %d ===\n\n", i);
try
{
g_alloc_counter = 100 + i;
g_alloc_counter = i;
using namespace lt;
run_test(
[](lt::session&, lt::alert const*) {},
@ -211,5 +231,12 @@ TORRENT_TEST(error_handling)
// continue, we won't exercise any new code paths
if (g_alloc_counter > 0) break;
}
// if this fails, we need to raise the limit in the loop above
TEST_CHECK(g_alloc_counter > 0);
// we don't want any part of the actual test framework to suffer from failed
// allocations, so bump the counter
g_alloc_counter = 1000000;
}

View File

@ -329,7 +329,6 @@ cached_piece_entry::~cached_piece_entry()
{
for (int i = 0; i < blocks_in_piece; ++i)
{
TORRENT_ASSERT(blocks[i].buf == nullptr);
TORRENT_ASSERT(!blocks[i].pending);
TORRENT_ASSERT(blocks[i].refcount == 0);
TORRENT_ASSERT(blocks[i].hashing_count == 0);

View File

@ -227,6 +227,9 @@ constexpr disk_job_flags_t disk_interface::cache_hit;
TORRENT_ASSERT(storage);
if (m_free_slots.empty())
{
// make sure there's always space in here to add another free slot.
// stopping a torrent should never fail because it needs to allocate memory
m_free_slots.reserve(m_torrents.size() + 1);
storage_index_t const idx = m_torrents.end_index();
m_torrents.emplace_back(std::move(storage));
m_torrents.back()->set_storage_index(idx);
@ -251,21 +254,15 @@ constexpr disk_job_flags_t disk_interface::cache_hit;
}
}
#if TORRENT_USE_ASSERTS
disk_io_thread::~disk_io_thread()
{
DLOG("destructing disk_io_thread\n");
#if TORRENT_USE_ASSERTS
// by now, all pieces should have been evicted
auto pieces = m_disk_cache.all_pieces();
TORRENT_ASSERT(pieces.first == pieces.second);
#endif
TORRENT_ASSERT(m_magic == 0x1337);
#if TORRENT_USE_ASSERTS
m_magic = 0xdead;
#endif
}
#endif
void disk_io_thread::abort(bool const wait)
{

View File

@ -182,6 +182,11 @@ namespace libtorrent {
, print_endpoint(local_ep).c_str());
}
#endif
// this counter should not be incremeneted until we know constructing this
// peer object can't fail anymore
if (m_connecting && t) t->inc_num_connecting(m_peer_info);
#if TORRENT_USE_ASSERTS
piece_failed = false;
m_in_constructor = false;
@ -372,8 +377,6 @@ namespace libtorrent {
// if this is an incoming connection, we're done here
if (!m_connecting) return;
if (m_connecting && t) t->inc_num_connecting(m_peer_info);
#ifndef TORRENT_DISABLE_LOGGING
if (should_log(peer_log_alert::outgoing))
{
@ -529,14 +532,14 @@ namespace libtorrent {
}
void peer_connection::peer_log(peer_log_alert::direction_t direction
, char const* event) const
, char const* event) const noexcept
{
peer_log(direction, event, "");
}
TORRENT_FORMAT(4,5)
void peer_connection::peer_log(peer_log_alert::direction_t direction
, char const* event, char const* fmt, ...) const
, char const* event, char const* fmt, ...) const noexcept try
{
TORRENT_ASSERT(is_single_thread());
@ -555,6 +558,7 @@ namespace libtorrent {
va_end(v);
}
catch (std::exception const&) {}
#endif
#ifndef TORRENT_DISABLE_EXTENSIONS
@ -797,8 +801,6 @@ namespace libtorrent {
// INVARIANT_CHECK;
TORRENT_ASSERT(!m_in_constructor);
TORRENT_ASSERT(m_disconnecting);
TORRENT_ASSERT(m_disconnect_started);
TORRENT_ASSERT(!m_destructed);
#if TORRENT_USE_ASSERTS
m_destructed = true;
@ -836,7 +838,6 @@ namespace libtorrent {
TORRENT_ASSERT(t || !m_connecting);
// we should really have dealt with this already
TORRENT_ASSERT(!m_connecting);
if (m_connecting)
{
m_counters.inc_stats_counter(counters::num_peers_half_open, -1);
@ -853,11 +854,6 @@ namespace libtorrent {
#endif
TORRENT_ASSERT(m_request_queue.empty());
TORRENT_ASSERT(m_download_queue.empty());
#if TORRENT_USE_ASSERTS
if (m_peer_info)
TORRENT_ASSERT(m_peer_info->connection == nullptr);
#endif
}
bool peer_connection::on_parole() const
@ -4385,7 +4381,7 @@ namespace libtorrent {
m_queued_time_critical = 0;
#if TORRENT_USE_INVARIANT_CHECKS
check_invariant();
try { check_invariant(); } catch (std::exception const&) {}
#endif
t->remove_peer(self());
@ -6326,10 +6322,6 @@ namespace libtorrent {
TORRENT_ASSERT(m_request_queue.empty());
TORRENT_ASSERT(m_disconnect_started);
}
else if (!m_in_constructor)
{
TORRENT_ASSERT(m_ses.has_peer(this));
}
TORRENT_ASSERT(m_outstanding_bytes >= 0);
if (t && t->valid_metadata() && !m_disconnecting)

View File

@ -882,12 +882,13 @@ namespace aux {
session_log(" aborting all connections (%d)", int(m_connections.size()));
#endif
// abort all connections
// keep in mind that connections that are not associated with a torrent
// will remove its entry from m_connections immediately, which means we
// can't iterate over it here
auto conns = m_connections;
for (auto const& p : conns)
for (connection_map::iterator i = m_connections.begin();
i != m_connections.end();)
{
peer_connection* p = (*i).get();
++i;
p->disconnect(errors::stopping_torrent, operation_t::bittorrent);
}
// close the listen sockets
for (auto const& l : m_listen_sockets)
@ -4533,7 +4534,7 @@ namespace {
}
TORRENT_FORMAT(2,3)
void session_impl::session_log(char const* fmt, ...) const
void session_impl::session_log(char const* fmt, ...) const noexcept try
{
if (!m_alerts.should_post<log_alert>()) return;
@ -4542,6 +4543,7 @@ namespace {
m_alerts.emplace_alert<log_alert>(fmt, v);
va_end(v);
}
catch (std::exception const&) {}
#endif
void session_impl::get_torrent_status(std::vector<torrent_status>* ret
@ -7147,7 +7149,7 @@ namespace {
return m_ses.alerts().should_post<log_alert>();
}
void tracker_logger::debug_log(const char* fmt, ...) const
void tracker_logger::debug_log(const char* fmt, ...) const noexcept try
{
if (!m_ses.alerts().should_post<log_alert>()) return;
@ -7156,5 +7158,6 @@ namespace {
m_ses.alerts().emplace_alert<log_alert>(fmt, v);
va_end(v);
}
catch (std::exception const&) {}
#endif // TORRENT_DISABLE_LOGGING
}}

View File

@ -4361,8 +4361,23 @@ namespace libtorrent {
// the torrent object from there
if (m_storage)
{
m_ses.disk_thread().async_stop_torrent(m_storage
, std::bind(&torrent::on_torrent_aborted, shared_from_this()));
try {
m_ses.disk_thread().async_stop_torrent(m_storage
, std::bind(&torrent::on_torrent_aborted, shared_from_this()));
}
catch (std::exception const& e)
{
TORRENT_UNUSED(e);
m_storage.reset();
#ifndef TORRENT_DISABLE_LOGGING
debug_log("Failed to flush disk cache: %s", e.what());
#endif
// clients may rely on this alert to be posted, so it's probably a
// good idea to post it here, even though we failed
// TODO: 3 should this alert have an error code in it?
if (alerts().should_post<cache_flushed_alert>())
alerts().emplace_alert<cache_flushed_alert>(get_handle());
}
}
else
{
@ -4401,6 +4416,10 @@ namespace libtorrent {
// have been destructed
if (m_peer_list) m_peer_list->clear();
m_connections.clear();
m_peers_to_disconnect.clear();
m_num_uploads = 0;
m_num_connecting = 0;
m_num_connecting_seeds = 0;
}
void torrent::set_super_seeding(bool on)
@ -6877,22 +6896,7 @@ namespace libtorrent {
peers_erased(st.erased);
m_peers_to_disconnect.reserve(m_connections.size() + 1);
TORRENT_ASSERT(sorted_find(m_connections, p) == m_connections.end());
TORRENT_ASSERT(m_iterating_connections == 0);
sorted_insert(m_connections, p);
update_want_peers();
update_want_tick();
if (p->peer_info_struct() && p->peer_info_struct()->seed)
{
TORRENT_ASSERT(m_num_seeds < 0xffff);
++m_num_seeds;
}
#ifndef TORRENT_DISABLE_LOGGING
debug_log("incoming peer (%d)", num_peers());
#endif
m_connections.reserve(m_connections.size() + 1);
#if TORRENT_USE_ASSERTS
error_code ec;
@ -6956,13 +6960,33 @@ namespace libtorrent {
if (m_share_mode)
recalc_share_mode();
// once we add the peer to our m_connections list, we can't throw an
// exception. That will end up violating an invariant between the session,
// torrent and peers
TORRENT_ASSERT(sorted_find(m_connections, p) == m_connections.end());
TORRENT_ASSERT(m_iterating_connections == 0);
sorted_insert(m_connections, p);
update_want_peers();
update_want_tick();
if (p->peer_info_struct() && p->peer_info_struct()->seed)
{
TORRENT_ASSERT(m_num_seeds < 0xffff);
++m_num_seeds;
}
#ifndef TORRENT_DISABLE_LOGGING
if (should_log())
debug_log("incoming peer (%d)", num_peers());
#endif
#ifndef TORRENT_DISABLE_LOGGING
if (should_log()) try
{
debug_log("ATTACHED CONNECTION \"%s\" connections: %d limit: %d"
, print_endpoint(p->remote()).c_str(), num_peers()
, m_max_connections);
}
catch (std::exception const&) {}
#endif
return true;
@ -7775,7 +7799,7 @@ namespace libtorrent {
TORRENT_ASSERT(is_single_thread());
// this fires during disconnecting peers
// if (is_paused()) TORRENT_ASSERT(num_peers() == 0 || m_graceful_pause_mode);
if (is_paused()) TORRENT_ASSERT(num_peers() == 0 || m_graceful_pause_mode);
int seeds = 0;
int num_uploads = 0;
@ -7784,10 +7808,6 @@ namespace libtorrent {
std::map<piece_block, int> num_requests;
for (peer_connection const* peer : *this)
{
#ifdef TORRENT_EXPENSIVE_INVARIANT_CHECKS
// make sure this peer is not a dangling pointer
TORRENT_ASSERT(m_ses.has_peer(peer));
#endif
peer_connection const& p = *peer;
if (p.is_connecting()) ++num_connecting;
@ -7813,16 +7833,19 @@ namespace libtorrent {
if (associated_torrent != this && associated_torrent != nullptr)
TORRENT_ASSERT_FAIL();
}
TORRENT_ASSERT(num_uploads == int(m_num_uploads));
TORRENT_ASSERT(seeds == int(m_num_seeds));
TORRENT_ASSERT(num_connecting == int(m_num_connecting));
TORRENT_ASSERT(num_connecting_seeds == int(m_num_connecting_seeds));
TORRENT_ASSERT(int(m_num_uploads) <= num_peers());
TORRENT_ASSERT(int(m_num_seeds) <= num_peers());
TORRENT_ASSERT(int(m_num_connecting) <= num_peers());
TORRENT_ASSERT(int(m_num_connecting_seeds) <= num_peers());
TORRENT_ASSERT(int(m_num_connecting) + int(m_num_seeds) >= int(m_num_connecting_seeds));
TORRENT_ASSERT(int(m_num_connecting) + int(m_num_seeds) - int(m_num_connecting_seeds) <= num_peers());
TORRENT_ASSERT_VAL(num_uploads == int(m_num_uploads), int(m_num_uploads) - num_uploads);
TORRENT_ASSERT_VAL(seeds == int(m_num_seeds), int(m_num_seeds) - seeds);
TORRENT_ASSERT_VAL(num_connecting == int(m_num_connecting), int(m_num_connecting) - num_connecting);
TORRENT_ASSERT_VAL(num_connecting_seeds == int(m_num_connecting_seeds)
, int(m_num_connecting_seeds) - num_connecting_seeds);
TORRENT_ASSERT_VAL(int(m_num_uploads) <= num_peers(), m_num_uploads - num_peers());
TORRENT_ASSERT_VAL(int(m_num_seeds) <= num_peers(), m_num_seeds - num_peers());
TORRENT_ASSERT_VAL(int(m_num_connecting) <= num_peers(), int(m_num_connecting) - num_peers());
TORRENT_ASSERT_VAL(int(m_num_connecting_seeds) <= num_peers(), int(m_num_connecting_seeds) - num_peers());
TORRENT_ASSERT_VAL(int(m_num_connecting) + int(m_num_seeds) >= int(m_num_connecting_seeds)
, int(m_num_connecting_seeds) - (int(m_num_connecting) + int(m_num_seeds)));
TORRENT_ASSERT_VAL(int(m_num_connecting) + int(m_num_seeds) - int(m_num_connecting_seeds) <= num_peers()
, num_peers() - (int(m_num_connecting) + int(m_num_seeds) - int(m_num_connecting_seeds)));
if (has_picker())
{
@ -10979,7 +11002,7 @@ namespace {
}
TORRENT_FORMAT(2,3)
void torrent::debug_log(char const* fmt, ...) const
void torrent::debug_log(char const* fmt, ...) const noexcept try
{
if (!alerts().should_post<torrent_log_alert>()) return;
@ -10989,6 +11012,7 @@ namespace {
const_cast<torrent*>(this)->get_handle(), fmt, v);
va_end(v);
}
catch (std::exception const&) {}
#endif
}

View File

@ -70,11 +70,11 @@ struct mock_peer_connection
virtual ~mock_peer_connection() = default;
#if !defined TORRENT_DISABLE_LOGGING
bool should_log(peer_log_alert::direction_t) const override
bool should_log(peer_log_alert::direction_t) const noexcept override
{ return true; }
void peer_log(peer_log_alert::direction_t dir, char const* event
, char const* fmt, ...) const override
, char const* fmt, ...) const noexcept override
{
va_list v;
va_start(v, fmt);