From 1b08a0be7fd4aec44548a62a5fc1627128d415c1 Mon Sep 17 00:00:00 2001 From: Vladimir Babin Date: Tue, 16 Jun 2026 12:32:49 +0800 Subject: [PATCH] fix(database): abort P39 cascade recovery when pop_block() makes no progress MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #117 added a no-progress guard to the three fork-switch pop loops in _push_block() but left the fourth pop loop — the P39 cascade-recovery path — unguarded. It has the identical deadlock: when pop_block()'s undo() is a no-op (committed/empty undo session while head is still above LIB), head_block_num() never decreases, so neither '> original_head' nor '> lib_num' can ever become false and the loop spins forever holding the global chainbase write lock — the same wedge (writer_held_ms climbing for hours) observed during the minority-fork reorg incident. Mirror #117's recovery-loop pattern: detect that head_block_id() did not change after pop_block() and break. The existing _fork_db.reset() + start_block() right after the loop already restores fork_db to the database head, so break leaves consistent state. This closes the last unguarded no-progress pop loop in _push_block(). --- libraries/chain/database.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libraries/chain/database.cpp b/libraries/chain/database.cpp index c98cb9776f..61cbfb6e49 100644 --- a/libraries/chain/database.cpp +++ b/libraries/chain/database.cpp @@ -2422,7 +2422,21 @@ namespace graphene { namespace chain { head_block_num() > lib_num) { ilog("P39 cascade recovery: popping head #${h} (restoring to #${t})", ("h", head_block_num())("t", original_head)); + block_id_type _pop_prev_id = head_block_id(); pop_block(); + if (head_block_id() == _pop_prev_id) { + // pop_block() made no progress (committed/empty undo session + // while head is above LIB): undo() reverts nothing, so + // head_block_num() never decreases and neither loop condition + // can become false. Without this check the loop spins forever + // holding the global chainbase write lock — the same wedge + // fixed for the fork-switch loops in #117, here in the P39 + // cascade-recovery path. Stop; the _fork_db.reset() below + // restores fork_db to the database head. + wlog("P39 cascade recovery: pop_block() made no progress at head #${h}. Stopping.", + ("h", head_block_num())); + break; + } } _fork_db.reset(); if (head_blk) {