I'm sometimes getting an std::bad_alloc for the following code:
typedef std::unordered_map<chash, block_extended_info> map_type;
map_type m_foo;
// the transgressor:
auto r = m_foo.insert(map_type::value_type(id, bei));
It happens some of the time while running test cases that use the method containing that line, and other times it doesn't. I had changed the code to use m_foo[id] = bei
, which caused it to stop happening for one compilation, but then when I recompiled again it failed, so I changed it to the above, which continued to fail. Clearly the issue is deeper than that.
I am fairly sure it's not a matter of running out of memory since I have top
running while the test cases are running and it doesn't get anywhere near filling up the memory.
What may be causing the std::bad_alloc? What in the details of chash
and block_extended_info
may be causing this? Objects of those types are copied and passed around all over the place in other parts of the code and that doesn't cause any issues.
Here's the definition of chash
:
class chash {
char data[32];
};
And the following is everything required to define block_extended_info
:
class csignature {
chash c, r;
};
enum foob { /* ... */ };
class ct {
uint64_t a, b;
foob c;
};
typedef boost::variant< /* structs with ints, maps, vectors of ints */ > txin_v;
typedef boost::variant< /* similar structs */ > txout_target_v;
struct tx_out {
uint64_t a;
txout_target_v b;
};
class transaction_prefix
{
public:
size_t version;
uint64_t unlock_time;
std::vector<uint8_t> extra;
protected:
std::vector<txin_v> a;
std::vector<tx_out> b;
std::vector<ct> c;
std::vector<ct> d;
};
class transaction: public transaction_prefix
{
public:
std::vector<std::vector<csignature> > signatures;
};
struct block_header
{
uint8_t major_version;
uint8_t minor_version;
uint64_t timestamp;
chash prev_id;
uint32_t nonce;
};
struct block: public block_header
{
transaction miner_tx;
std::vector<chash> tx_hashes;
uint16_t food_id;
csignature food_sig;
};
struct block_extended_info
{
block bl;
uint64_t height;
size_t block_cumulative_size;
uint64_t cumulative_difficulty;
uint64_t already_generated_coins;
};
Stepping around the relevant area in gdb, here are the latest few lines that lead up to the bad_alloc:
cryptonote::transaction::transaction(cryptonote::transaction const&) () at /usr/include/c++/4.8/bits/stl_vector.h:313
313 { this->_M_impl._M_finish =
646 { return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }
91 : _Tp_alloc_type(__a), _M_start(0), _M_finish(0), _M_end_of_storage(0)
168 { return __n != 0 ? _M_impl.allocate(__n) : 0; }
183 this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
181 this->_M_impl._M_start = this->_M_allocate(__n);
182 this->_M_impl._M_finish = this->_M_impl._M_start;
183 this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
310 vector(const vector& __x)
117 __uninit_copy(__first, __last, __result);
cryptonote::tx_out* std::__uninitialized_copy<false>::__uninit_copy<__gnu_cxx::__normal_iterator<cryptonote::tx_out const*, std::vector<cryptonote::tx_out, std::allocator<cryptonote::tx_out> > >, cryptonote::tx_out*>(__gnu_cxx::__normal_iterator<cryptonote::tx_out const*, std::vector<cryptonote::tx_out, std::allocator<cryptonote::tx_out> > >, __gnu_cxx::__normal_iterator<cryptonote::tx_out const*, std::vector<cryptonote::tx_out, std::allocator<cryptonote::tx_out> > >, cryptonote::tx_out*) ()
at /usr/include/c++/4.8/bits/stl_uninitialized.h:68
68 __uninit_copy(_InputIterator __first, _InputIterator __last,
74 for (; __first != __last; ++__first, ++__cur)
83 }
cryptonote::transaction::transaction(cryptonote::transaction const&) () at /usr/include/c++/4.8/bits/stl_vector.h:313
313 { this->_M_impl._M_finish =
646 { return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }
91 : _Tp_alloc_type(__a), _M_start(0), _M_finish(0), _M_end_of_storage(0)
646 { return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }
168 { return __n != 0 ? _M_impl.allocate(__n) : 0; }
101 if (__n > this->max_size())
104 return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp)));
183 this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
181 this->_M_impl._M_start = this->_M_allocate(__n);
182 this->_M_impl._M_finish = this->_M_impl._M_start;
183 this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
310 vector(const vector& __x)
74 for (; __first != __last; ++__first, ++__cur)
71 _ForwardIterator __cur = __result;
75 { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
748 ++_M_current;
74 for (; __first != __last; ++__first, ++__cur)
313 { this->_M_impl._M_finish =
646 { return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }
91 : _Tp_alloc_type(__a), _M_start(0), _M_finish(0), _M_end_of_storage(0)
646 { return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }
168 { return __n != 0 ? _M_impl.allocate(__n) : 0; }
183 this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
181 this->_M_impl._M_start = this->_M_allocate(__n);
182 this->_M_impl._M_finish = this->_M_impl._M_start;
183 this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
310 vector(const vector& __x)
74 for (; __first != __last; ++__first, ++__cur)
570 class transaction: public transaction_prefix
313 { this->_M_impl._M_finish =
646 { return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }
91 : _Tp_alloc_type(__a), _M_start(0), _M_finish(0), _M_end_of_storage(0)
646 { return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }
168 { return __n != 0 ? _M_impl.allocate(__n) : 0; }
101 if (__n > this->max_size())
104 return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp)));
181 this->_M_impl._M_start = this->_M_allocate(__n);
183 this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
181 this->_M_impl._M_start = this->_M_allocate(__n);
182 this->_M_impl._M_finish = this->_M_impl._M_start;
183 this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
310 vector(const vector& __x)
74 for (; __first != __last; ++__first, ++__cur)
101 if (__n > this->max_size())
75 { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
646 { return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }
91 : _Tp_alloc_type(__a), _M_start(0), _M_finish(0), _M_end_of_storage(0)
168 { return __n != 0 ? _M_impl.allocate(__n) : 0; }
101 if (__n > this->max_size())
102 std::__throw_bad_alloc();
As πάντα ῥεῖ suspected, the problem was elsewhere.
The code I listed was in a function called add_block_as_invalid(...)
. The call site looked something like this:
// parameter, iterators into m_alternative_chains
std::list<blocks_ext_by_hash::iterator>& alt_chain;
for(auto alt_ch_iter = alt_chain.begin();
alt_ch_iter != alt_chain.end();
alt_ch_iter++)
{
// ....
auto ch_ent = *alt_ch_iter;
// ....
m_alternative_chains.erase(ch_ent);
for(auto alt_ch_to_orph_iter = ++alt_ch_iter;
alt_ch_to_orph_iter != alt_chain.end();
alt_ch_to_orph_iter++)
{
add_block_as_invalid((*alt_ch_iter)->second, (*alt_ch_iter)->first);
m_alternative_chains.erase(*alt_ch_to_orph_iter);
}
}
The call to add_block_as_invalid
kept dereferencing alt_ch_iter
, which had already been erased.