init: Signal m_tip_block_cv on Ctrl-C

Signal m_tip_block_cv when Ctrl-C is pressed or SIGTERM is received, the same
way it is currently signalled when the `stop` RPC is called. This lets RPC
calls like `waitforblockheight` and IPC calls like `waitTipChanged` be
interrupted, instead of waiting for their original timeouts and delaying
shutdown.

Historical notes:

- The behavior where `stop` RPC signals `m_tip_block_cv`, but CTRL-C does not,
  has been around since the condition variable was introduced in #30409
  (7eccdaf160).
- The signaling was later moved without changing behavior in #30967
  (5ca28ef28b). This commit moves it again to
  the Interrupt() function, which is probably the place it should have been
  added initially, so it works for Ctrl-C shutdowns as well as `stop`
  shutdowns.
- A Qt shutdown bug calling wait methods was fixed previously in #18452
  (da73f1513a), and this change updates that
  fix to avoid the hang happening again in Qt.
This commit is contained in:
Ryan Ofsky 2025-10-01 13:00:33 -04:00
parent 6a29f79006
commit c25a5e670b
3 changed files with 8 additions and 19 deletions

View File

@ -215,8 +215,6 @@ void InitContext(NodeContext& node)
node.shutdown_request = [&node] { node.shutdown_request = [&node] {
assert(node.shutdown_signal); assert(node.shutdown_signal);
if (!(*node.shutdown_signal)()) return false; if (!(*node.shutdown_signal)()) return false;
// Wake any threads that may be waiting for the tip to change.
if (node.notifications) WITH_LOCK(node.notifications->m_tip_block_mutex, node.notifications->m_tip_block_cv.notify_all());
return true; return true;
}; };
} }
@ -267,6 +265,8 @@ void Interrupt(NodeContext& node)
#if HAVE_SYSTEM #if HAVE_SYSTEM
ShutdownNotify(*node.args); ShutdownNotify(*node.args);
#endif #endif
// Wake any threads that may be waiting for the tip to change.
if (node.notifications) WITH_LOCK(node.notifications->m_tip_block_mutex, node.notifications->m_tip_block_cv.notify_all());
InterruptHTTPServer(); InterruptHTTPServer();
InterruptHTTPRPC(); InterruptHTTPRPC();
InterruptRPC(); InterruptRPC();

View File

@ -132,7 +132,6 @@ public:
} }
void appShutdown() override void appShutdown() override
{ {
Interrupt(*m_context);
Shutdown(*m_context); Shutdown(*m_context);
} }
void startShutdown() override void startShutdown() override
@ -141,12 +140,7 @@ public:
if (!(Assert(ctx.shutdown_request))()) { if (!(Assert(ctx.shutdown_request))()) {
LogError("Failed to send shutdown signal\n"); LogError("Failed to send shutdown signal\n");
} }
Interrupt(*m_context);
// Stop RPC for clean shutdown if any of waitfor* commands is executed.
if (args().GetBoolArg("-server", false)) {
InterruptRPC();
StopRPC();
}
} }
bool shutdownRequested() override { return ShutdownRequested(*Assert(m_context)); }; bool shutdownRequested() override { return ShutdownRequested(*Assert(m_context)); };
bool isSettingIgnored(const std::string& name) override bool isSettingIgnored(const std::string& name) override

View File

@ -12,7 +12,6 @@ import signal
import subprocess import subprocess
import time import time
from test_framework.authproxy import JSONRPCException
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.test_node import ( from test_framework.test_node import (
BITCOIN_PID_FILENAME_DEFAULT, BITCOIN_PID_FILENAME_DEFAULT,
@ -251,9 +250,8 @@ class InitTest(BitcoinTestFramework):
terminal. (This can be different than the node shutdown sequence that terminal. (This can be different than the node shutdown sequence that
happens when the stop RPC is sent.) happens when the stop RPC is sent.)
Currently when the break signal is sent, it does not interrupt the The waitforblockheight call should be interrupted and return right away,
waitforblockheight RPC call, and the node does not exit until it times and not time out."""
out."""
self.log.info("Testing waitforblockheight RPC call followed by break signal") self.log.info("Testing waitforblockheight RPC call followed by break signal")
node = self.nodes[0] node = self.nodes[0]
@ -287,12 +285,9 @@ class InitTest(BitcoinTestFramework):
node.process.send_signal(signal.SIGTERM) node.process.send_signal(signal.SIGTERM)
node.process.wait() node.process.wait()
try: result = fut.result()
result = fut.result() self.log.debug(f"waitforblockheight returned {result!r}")
raise Exception(f"waitforblockheight returned {result!r}") assert_equal(result["height"], current_height)
except JSONRPCException as e:
self.log.debug(f"waitforblockheight raised {e!r}")
assert_equal(e.error['code'], -344) # -344 is RPC timeout
node.wait_until_stopped() node.wait_until_stopped()
def run_test(self): def run_test(self):