Skip to content

Commit 7159bc3

Browse files
committed
Fix 4GB RSS crash: bound LevelDB load + prune evicted shares from disk
LevelDB accumulated shares forever (remove_share existed but was never called). On restart, all historical shares loaded into memory unbounded. Three fixes: - load_persisted_shares() now loads only newest chain_length*2+10 shares and prunes older entries from LevelDB on startup - run_think() trim now collects evicted hashes via new output parameter on ShareChain::trim() and removes them from LevelDB - SharechainStorage::remove_share() exposed to node layer - RSS logged in every run_think() cycle for trend visibility
1 parent 3200e01 commit 7159bc3

4 files changed

Lines changed: 79 additions & 13 deletions

File tree

src/c2pool/storage/sharechain_storage.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,19 @@ bool SharechainStorage::has_share(const uint256& hash)
155155
if (!m_leveldb_store) {
156156
return false;
157157
}
158-
158+
159159
return m_leveldb_store->has_share(hash);
160160
}
161161

162+
bool SharechainStorage::remove_share(const uint256& hash)
163+
{
164+
if (!m_leveldb_store) {
165+
return false;
166+
}
167+
168+
return m_leveldb_store->remove_share(hash);
169+
}
170+
162171
void SharechainStorage::log_storage_stats()
163172
{
164173
if (!m_leveldb_store) {

src/c2pool/storage/sharechain_storage.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ class SharechainStorage {
9898
* @return True if share exists
9999
*/
100100
bool has_share(const uint256& hash);
101+
102+
/**
103+
* @brief Remove a share from storage
104+
* @param hash Share hash to remove
105+
* @return True if successfully removed
106+
*/
107+
bool remove_share(const uint256& hash);
101108

102109
/**
103110
* @brief Log storage statistics

src/impl/ltc/node.cpp

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -576,17 +576,32 @@ void NodeImpl::load_persisted_shares()
576576
if (!m_storage || !m_storage->is_available())
577577
return;
578578

579-
// load_sharechain iterates the height index and loads each share
580-
auto hashes = m_storage->get_shares_by_height_range(0, UINT64_MAX);
581-
if (hashes.empty())
579+
// load_sharechain iterates the height index and loads each share.
580+
// Limit to keep_per_head shares to avoid loading unbounded history
581+
// into memory (LevelDB is never pruned, so it accumulates forever).
582+
auto all_hashes = m_storage->get_shares_by_height_range(0, UINT64_MAX);
583+
if (all_hashes.empty())
582584
{
583585
LOG_INFO << "No persisted shares found in LevelDB";
584586
return;
585587
}
586588

589+
const size_t keep_per_head = PoolConfig::chain_length() * 2 + 10;
590+
const size_t total_in_db = all_hashes.size();
591+
592+
// Only load the most recent shares (highest height = end of vector)
593+
size_t skip = 0;
594+
if (total_in_db > keep_per_head)
595+
{
596+
skip = total_in_db - keep_per_head;
597+
LOG_INFO << "[Pool] LevelDB has " << total_in_db << " shares, loading only newest "
598+
<< keep_per_head << " (skipping " << skip << " old shares)";
599+
}
600+
587601
int loaded = 0;
588-
for (const auto& hash : hashes)
602+
for (size_t i = skip; i < total_in_db; ++i)
589603
{
604+
const auto& hash = all_hashes[i];
590605
std::vector<uint8_t> data;
591606
uint256 prev; uint64_t height, ts; uint256 work, target; bool orphan;
592607
if (!m_storage->load_share(hash, data, prev, height, ts, work, target, orphan))
@@ -612,12 +627,25 @@ void NodeImpl::load_persisted_shares()
612627
}
613628
catch (const std::exception& e)
614629
{
615-
LOG_WARNING << "Failed to load share " << hash.ToString().substr(0, 16)
616-
<< "... from LevelDB: " << e.what();
630+
LOG_WARNING << "Failed to load share " << hash.ToString()
631+
<< " from LevelDB: " << e.what();
617632
}
618633
}
619634

620-
LOG_INFO << "[Pool] Loaded " << loaded << " shares from LevelDB storage";
635+
LOG_INFO << "[Pool] Loaded " << loaded << " shares from LevelDB storage"
636+
<< " (DB total: " << total_in_db << ", limit: " << keep_per_head << ")";
637+
638+
// Prune old shares from LevelDB that we skipped
639+
if (skip > 0)
640+
{
641+
size_t pruned = 0;
642+
for (size_t i = 0; i < skip; ++i)
643+
{
644+
if (m_storage->remove_share(all_hashes[i]))
645+
++pruned;
646+
}
647+
LOG_INFO << "[Pool] Pruned " << pruned << " old shares from LevelDB";
648+
}
621649
}
622650

623651
void NodeImpl::start_outbound_connections()
@@ -676,7 +704,8 @@ void NodeImpl::run_think()
676704

677705
LOG_INFO << "[Pool] run_think(): chain=" << m_tracker.chain.size()
678706
<< " verified=" << m_tracker.verified.size()
679-
<< " heads=" << m_tracker.chain.get_heads().size();
707+
<< " heads=" << m_tracker.chain.get_heads().size()
708+
<< " rss=" << get_rss_mb() << "MB";
680709

681710
// Capture block_rel_height fn by value for thread safety
682711
auto block_rel_height = m_block_rel_height_fn
@@ -701,21 +730,36 @@ void NodeImpl::run_think()
701730
{
702731
// Trim stale shares (on ioc thread — safe with phase2)
703732
const size_t keep_per_head = PoolConfig::chain_length() * 2 + 10;
704-
auto trim_chain = [&](auto& sc, const char* label, bool owns_data = true) {
733+
std::vector<uint256> evicted_from_chain;
734+
auto trim_chain = [&](auto& sc, const char* label, bool owns_data = true,
735+
std::vector<uint256>* evicted = nullptr) {
705736
if (sc.size() <= keep_per_head)
706737
return;
707738
auto heads_copy = sc.get_heads();
708739
size_t total_removed = 0;
709740
for (auto& [head_hash, tail_hash] : heads_copy) {
710-
auto removed = sc.trim(head_hash, keep_per_head, owns_data);
741+
auto removed = sc.trim(head_hash, keep_per_head, owns_data, evicted);
711742
total_removed += removed;
712743
}
713744
if (total_removed > 0)
714745
LOG_INFO << "[Pool] Trimmed " << total_removed << " old shares from " << label
715746
<< " (now " << sc.size() << ")";
716747
};
717748
trim_chain(m_tracker.verified, "verified", /*owns_data=*/false);
718-
trim_chain(m_tracker.chain, "chain");
749+
trim_chain(m_tracker.chain, "chain", /*owns_data=*/true, &evicted_from_chain);
750+
751+
// Prune evicted shares from LevelDB
752+
if (!evicted_from_chain.empty() && m_storage && m_storage->is_available())
753+
{
754+
size_t pruned = 0;
755+
for (const auto& h : evicted_from_chain)
756+
{
757+
if (m_storage->remove_share(h))
758+
++pruned;
759+
}
760+
if (pruned > 0)
761+
LOG_INFO << "[Pool] Pruned " << pruned << " evicted shares from LevelDB";
762+
}
719763

720764
// Ban peers that provided invalid/unverifiable shares.
721765
// Skip localhost:0 — that's our own locally created shares

src/sharechain/sharechain.hpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,9 @@ class ShareChain
426426
/// @param owns_data When true (default) the evicted share data is
427427
/// destroyed. Set to false for chains that borrow share
428428
/// pointers from another chain (e.g. verified borrows from chain).
429-
size_t trim(const hash_t& head, size_t max_size, bool owns_data = true)
429+
/// @param evicted_hashes If non-null, hashes of removed shares are appended here.
430+
size_t trim(const hash_t& head, size_t max_size, bool owns_data = true,
431+
std::vector<hash_t>* evicted_hashes = nullptr)
430432
{
431433
if (!m_shares.contains(head) || max_size == 0)
432434
return 0;
@@ -494,6 +496,10 @@ class ShareChain
494496
}
495497
}
496498

499+
// Collect evicted hashes for caller (e.g. LevelDB pruning)
500+
if (evicted_hashes)
501+
evicted_hashes->insert(evicted_hashes->end(), to_remove.begin(), to_remove.end());
502+
497503
return to_remove.size();
498504
}
499505

0 commit comments

Comments
 (0)