Merge bitcoin/bitcoin#27708: Return EXIT_FAILURE on post-init fatal errors

61c569ab60 refactor: decouple early return commands from AppInit (furszy)
4927167f85 gui: return EXIT_FAILURE on post-init fatal errors (furszy)
3b2c61e819 Return EXIT_FAILURE on post-init fatal errors (furszy)
3c06926cf2 refactor: index: use `AbortNode` in fatal error helper (Sebastian Falbesoner)
9ddf7e03a3 move ThreadImport ABC error to use AbortNode (furszy)

Pull request description:

  It seems odd to return `EXIT_SUCCESS` when the node aborted execution due a fatal internal error
  or any post-init problem that triggers an unrequested shutdown.

  e.g. blocks or coins db I/O errors, disconnect block failure, failure during thread import (external
  blocks loading process error), among others.

ACKs for top commit:
  TheCharlatan:
    ACK 61c569ab60
  ryanofsky:
    Code review ACK 61c569ab60
  pinheadmz:
    ACK 61c569ab60
  theStack:
    Code-review ACK 61c569ab60

Tree-SHA512: 18a59c3acc1c6d12cbc74a20a401e89659740c6477fccb59070c9f97922dfe588468e9e5eef56c5f395762187c34179a5e3954aa5b844787fa13da2e666c63d3
This commit is contained in:
Ryan Ofsky 2023-06-12 12:13:55 -04:00
commit c92fd63886
No known key found for this signature in database
GPG key ID: 46800E30FC748A66
14 changed files with 85 additions and 56 deletions

View file

@ -112,20 +112,30 @@ int fork_daemon(bool nochdir, bool noclose, TokenPipeEnd& endpoint)
#endif #endif
static bool AppInit(NodeContext& node, int argc, char* argv[]) static bool ParseArgs(ArgsManager& args, int argc, char* argv[])
{ {
bool fRet = false;
util::ThreadSetInternalName("init");
// If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's main() // If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's main()
ArgsManager& args = *Assert(node.args);
SetupServerArgs(args); SetupServerArgs(args);
std::string error; std::string error;
if (!args.ParseParameters(argc, argv, error)) { if (!args.ParseParameters(argc, argv, error)) {
return InitError(Untranslated(strprintf("Error parsing command line arguments: %s", error))); return InitError(Untranslated(strprintf("Error parsing command line arguments: %s", error)));
} }
if (auto error = common::InitConfig(args)) {
return InitError(error->message, error->details);
}
// Error out when loose non-argument tokens are encountered on command line
for (int i = 1; i < argc; i++) {
if (!IsSwitchChar(argv[i][0])) {
return InitError(Untranslated(strprintf("Command line contains unexpected token '%s', see bitcoind -h for a list of options.", argv[i])));
}
}
return true;
}
static bool ProcessInitCommands(ArgsManager& args)
{
// Process help and version before taking care about datadir // Process help and version before taking care about datadir
if (HelpRequested(args) || args.IsArgSet("-version")) { if (HelpRequested(args) || args.IsArgSet("-version")) {
std::string strUsage = PACKAGE_NAME " version " + FormatFullVersion() + "\n"; std::string strUsage = PACKAGE_NAME " version " + FormatFullVersion() + "\n";
@ -142,6 +152,14 @@ static bool AppInit(NodeContext& node, int argc, char* argv[])
return true; return true;
} }
return false;
}
static bool AppInit(NodeContext& node)
{
bool fRet = false;
ArgsManager& args = *Assert(node.args);
#if HAVE_DECL_FORK #if HAVE_DECL_FORK
// Communication with parent after daemonizing. This is used for signalling in the following ways: // Communication with parent after daemonizing. This is used for signalling in the following ways:
// - a boolean token is sent when the initialization process (all the Init* functions) have finished to indicate // - a boolean token is sent when the initialization process (all the Init* functions) have finished to indicate
@ -153,23 +171,12 @@ static bool AppInit(NodeContext& node, int argc, char* argv[])
std::any context{&node}; std::any context{&node};
try try
{ {
if (auto error = common::InitConfig(args)) {
return InitError(error->message, error->details);
}
// Error out when loose non-argument tokens are encountered on command line
for (int i = 1; i < argc; i++) {
if (!IsSwitchChar(argv[i][0])) {
return InitError(Untranslated(strprintf("Command line contains unexpected token '%s', see bitcoind -h for a list of options.", argv[i])));
}
}
// -server defaults to true for bitcoind but not for the GUI so do this here // -server defaults to true for bitcoind but not for the GUI so do this here
args.SoftSetBoolArg("-server", true); args.SoftSetBoolArg("-server", true);
// Set this early so that parameter interactions go to console // Set this early so that parameter interactions go to console
InitLogging(args); InitLogging(args);
InitParameterInteraction(args); InitParameterInteraction(args);
if (!AppInitBasicSetup(args)) { if (!AppInitBasicSetup(args, node.exit_status)) {
// InitError will have been called with detailed error, which ends up on console // InitError will have been called with detailed error, which ends up on console
return false; return false;
} }
@ -236,12 +243,6 @@ static bool AppInit(NodeContext& node, int argc, char* argv[])
} }
#endif #endif
SetSyscallSandboxPolicy(SyscallSandboxPolicy::SHUTOFF); SetSyscallSandboxPolicy(SyscallSandboxPolicy::SHUTOFF);
if (fRet) {
WaitForShutdown();
}
Interrupt(node);
Shutdown(node);
return fRet; return fRet;
} }
@ -264,5 +265,22 @@ MAIN_FUNCTION
// Connect bitcoind signal handlers // Connect bitcoind signal handlers
noui_connect(); noui_connect();
return (AppInit(node, argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE); util::ThreadSetInternalName("init");
// Interpret command line arguments
ArgsManager& args = *Assert(node.args);
if (!ParseArgs(args, argc, argv)) return EXIT_FAILURE;
// Process early info return commands such as -help or -version
if (ProcessInitCommands(args)) return EXIT_SUCCESS;
// Start application
if (AppInit(node)) {
WaitForShutdown();
} else {
node.exit_status = EXIT_FAILURE;
}
Interrupt(node);
Shutdown(node);
return node.exit_status;
} }

View file

@ -33,11 +33,7 @@ constexpr auto SYNC_LOCATOR_WRITE_INTERVAL{30s};
template <typename... Args> template <typename... Args>
static void FatalError(const char* fmt, const Args&... args) static void FatalError(const char* fmt, const Args&... args)
{ {
std::string strMessage = tfm::format(fmt, args...); AbortNode(tfm::format(fmt, args...));
SetMiscWarning(Untranslated(strMessage));
LogPrintf("*** %s\n", strMessage);
InitError(_("A fatal internal error occurred, see debug.log for details"));
StartShutdown();
} }
CBlockLocator GetLocator(interfaces::Chain& chain, const uint256& block_hash) CBlockLocator GetLocator(interfaces::Chain& chain, const uint256& block_hash)

View file

@ -800,7 +800,7 @@ std::set<BlockFilterType> g_enabled_filter_types;
std::terminate(); std::terminate();
}; };
bool AppInitBasicSetup(const ArgsManager& args) bool AppInitBasicSetup(const ArgsManager& args, std::atomic<int>& exit_status)
{ {
// ********************************************************* Step 1: setup // ********************************************************* Step 1: setup
#ifdef _MSC_VER #ifdef _MSC_VER
@ -814,7 +814,7 @@ bool AppInitBasicSetup(const ArgsManager& args)
// Enable heap terminate-on-corruption // Enable heap terminate-on-corruption
HeapSetInformation(nullptr, HeapEnableTerminationOnCorruption, nullptr, 0); HeapSetInformation(nullptr, HeapEnableTerminationOnCorruption, nullptr, 0);
#endif #endif
if (!InitShutdownState()) { if (!InitShutdownState(exit_status)) {
return InitError(Untranslated("Initializing wait-for-shutdown state failed.")); return InitError(Untranslated("Initializing wait-for-shutdown state failed."));
} }

View file

@ -38,7 +38,7 @@ void InitParameterInteraction(ArgsManager& args);
* @note This can be done before daemonization. Do not call Shutdown() if this function fails. * @note This can be done before daemonization. Do not call Shutdown() if this function fails.
* @pre Parameters should be parsed and config file should be read. * @pre Parameters should be parsed and config file should be read.
*/ */
bool AppInitBasicSetup(const ArgsManager& args); bool AppInitBasicSetup(const ArgsManager& args, std::atomic<int>& exit_status);
/** /**
* Initialization: parameter interaction. * Initialization: parameter interaction.
* @note This can be done before daemonization. Do not call Shutdown() if this function fails. * @note This can be done before daemonization. Do not call Shutdown() if this function fails.

View file

@ -80,6 +80,9 @@ public:
//! Get warnings. //! Get warnings.
virtual bilingual_str getWarnings() = 0; virtual bilingual_str getWarnings() = 0;
//! Get exit status.
virtual int getExitStatus() = 0;
// Get log flags. // Get log flags.
virtual uint32_t getLogCategories() = 0; virtual uint32_t getLogCategories() = 0;

View file

@ -928,8 +928,7 @@ void ThreadImport(ChainstateManager& chainman, std::vector<fs::path> vImportFile
for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) { for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
BlockValidationState state; BlockValidationState state;
if (!chainstate->ActivateBestChain(state, nullptr)) { if (!chainstate->ActivateBestChain(state, nullptr)) {
LogPrintf("Failed to connect best block (%s)\n", state.ToString()); AbortNode(strprintf("Failed to connect best block (%s)", state.ToString()));
StartShutdown();
return; return;
} }
} }

View file

@ -7,7 +7,9 @@
#include <kernel/context.h> #include <kernel/context.h>
#include <atomic>
#include <cassert> #include <cassert>
#include <cstdlib>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <vector> #include <vector>
@ -65,6 +67,7 @@ struct NodeContext {
std::unique_ptr<CScheduler> scheduler; std::unique_ptr<CScheduler> scheduler;
std::function<void()> rpc_interruption_point = [] {}; std::function<void()> rpc_interruption_point = [] {};
std::unique_ptr<KernelNotifications> notifications; std::unique_ptr<KernelNotifications> notifications;
std::atomic<int> exit_status{EXIT_SUCCESS};
//! Declare default constructor and destructor that are not inline, so code //! Declare default constructor and destructor that are not inline, so code
//! instantiating the NodeContext struct doesn't need to #include class //! instantiating the NodeContext struct doesn't need to #include class

View file

@ -89,10 +89,11 @@ public:
void initLogging() override { InitLogging(args()); } void initLogging() override { InitLogging(args()); }
void initParameterInteraction() override { InitParameterInteraction(args()); } void initParameterInteraction() override { InitParameterInteraction(args()); }
bilingual_str getWarnings() override { return GetWarnings(true); } bilingual_str getWarnings() override { return GetWarnings(true); }
int getExitStatus() override { return Assert(m_context)->exit_status.load(); }
uint32_t getLogCategories() override { return LogInstance().GetCategoryMask(); } uint32_t getLogCategories() override { return LogInstance().GetCategoryMask(); }
bool baseInitialize() override bool baseInitialize() override
{ {
if (!AppInitBasicSetup(args())) return false; if (!AppInitBasicSetup(args(), Assert(context())->exit_status)) return false;
if (!AppInitParameterInteraction(args(), /*use_syscall_sandbox=*/false)) return false; if (!AppInitParameterInteraction(args(), /*use_syscall_sandbox=*/false)) return false;
m_context->kernel = std::make_unique<kernel::Context>(); m_context->kernel = std::make_unique<kernel::Context>();
@ -105,7 +106,10 @@ public:
} }
bool appInitMain(interfaces::BlockAndHeaderTipInfo* tip_info) override bool appInitMain(interfaces::BlockAndHeaderTipInfo* tip_info) override
{ {
return AppInitMain(*m_context, tip_info); if (AppInitMain(*m_context, tip_info)) return true;
// Error during initialization, set exit status before continue
m_context->exit_status.store(EXIT_FAILURE);
return false;
} }
void appShutdown() override void appShutdown() override
{ {

View file

@ -9,6 +9,7 @@
#include <qt/bitcoin.h> #include <qt/bitcoin.h>
#include <chainparams.h> #include <chainparams.h>
#include <node/context.h>
#include <common/args.h> #include <common/args.h>
#include <common/init.h> #include <common/init.h>
#include <common/system.h> #include <common/system.h>
@ -397,9 +398,7 @@ void BitcoinApplication::initializeResult(bool success, interfaces::BlockAndHead
{ {
qDebug() << __func__ << ": Initialization result: " << success; qDebug() << __func__ << ": Initialization result: " << success;
// Set exit result. if (success) {
returnValue = success ? EXIT_SUCCESS : EXIT_FAILURE;
if(success) {
delete m_splash; delete m_splash;
m_splash = nullptr; m_splash = nullptr;
@ -653,7 +652,6 @@ int GuiMain(int argc, char* argv[])
app.InitPruneSetting(prune_MiB); app.InitPruneSetting(prune_MiB);
} }
int rv = EXIT_SUCCESS;
try try
{ {
app.createWindow(networkStyle.data()); app.createWindow(networkStyle.data());
@ -666,10 +664,9 @@ int GuiMain(int argc, char* argv[])
WinShutdownMonitor::registerShutdownBlockReason(QObject::tr("%1 didn't yet exit safely…").arg(PACKAGE_NAME), (HWND)app.getMainWinId()); WinShutdownMonitor::registerShutdownBlockReason(QObject::tr("%1 didn't yet exit safely…").arg(PACKAGE_NAME), (HWND)app.getMainWinId());
#endif #endif
app.exec(); app.exec();
rv = app.getReturnValue();
} else { } else {
// A dialog with detailed error will have been shown by InitError() // A dialog with detailed error will have been shown by InitError()
rv = EXIT_FAILURE; return EXIT_FAILURE;
} }
} catch (const std::exception& e) { } catch (const std::exception& e) {
PrintExceptionContinue(&e, "Runaway exception"); PrintExceptionContinue(&e, "Runaway exception");
@ -678,5 +675,5 @@ int GuiMain(int argc, char* argv[])
PrintExceptionContinue(nullptr, "Runaway exception"); PrintExceptionContinue(nullptr, "Runaway exception");
app.handleRunawayException(QString::fromStdString(app.node().getWarnings().translated)); app.handleRunawayException(QString::fromStdString(app.node().getWarnings().translated));
} }
return rv; return app.node().getExitStatus();
} }

View file

@ -62,9 +62,6 @@ public:
/// Request core initialization /// Request core initialization
void requestInitialize(); void requestInitialize();
/// Get process return value
int getReturnValue() const { return returnValue; }
/// Get window identifier of QMainWindow (BitcoinGUI) /// Get window identifier of QMainWindow (BitcoinGUI)
WId getMainWinId() const; WId getMainWinId() const;
@ -104,7 +101,6 @@ private:
PaymentServer* paymentServer{nullptr}; PaymentServer* paymentServer{nullptr};
WalletController* m_wallet_controller{nullptr}; WalletController* m_wallet_controller{nullptr};
#endif #endif
int returnValue{0};
const PlatformStyle* platformStyle{nullptr}; const PlatformStyle* platformStyle{nullptr};
std::unique_ptr<QWidget> shutdownWindow; std::unique_ptr<QWidget> shutdownWindow;
SplashScreen* m_splash = nullptr; SplashScreen* m_splash = nullptr;

View file

@ -11,6 +11,7 @@
#include <logging.h> #include <logging.h>
#include <node/interface_ui.h> #include <node/interface_ui.h>
#include <util/check.h>
#include <util/tokenpipe.h> #include <util/tokenpipe.h>
#include <warnings.h> #include <warnings.h>
@ -20,6 +21,8 @@
#include <condition_variable> #include <condition_variable>
#endif #endif
static std::atomic<int>* g_exit_status{nullptr};
bool AbortNode(const std::string& strMessage, bilingual_str user_message) bool AbortNode(const std::string& strMessage, bilingual_str user_message)
{ {
SetMiscWarning(Untranslated(strMessage)); SetMiscWarning(Untranslated(strMessage));
@ -28,6 +31,7 @@ bool AbortNode(const std::string& strMessage, bilingual_str user_message)
user_message = _("A fatal internal error occurred, see debug.log for details"); user_message = _("A fatal internal error occurred, see debug.log for details");
} }
InitError(user_message); InitError(user_message);
Assert(g_exit_status)->store(EXIT_FAILURE);
StartShutdown(); StartShutdown();
return false; return false;
} }
@ -44,8 +48,9 @@ static TokenPipeEnd g_shutdown_r;
static TokenPipeEnd g_shutdown_w; static TokenPipeEnd g_shutdown_w;
#endif #endif
bool InitShutdownState() bool InitShutdownState(std::atomic<int>& exit_status)
{ {
g_exit_status = &exit_status;
#ifndef WIN32 #ifndef WIN32
std::optional<TokenPipe> pipe = TokenPipe::Make(); std::optional<TokenPipe> pipe = TokenPipe::Make();
if (!pipe) return false; if (!pipe) return false;

View file

@ -8,13 +8,15 @@
#include <util/translation.h> // For bilingual_str #include <util/translation.h> // For bilingual_str
#include <atomic>
/** Abort with a message */ /** Abort with a message */
bool AbortNode(const std::string& strMessage, bilingual_str user_message = bilingual_str{}); bool AbortNode(const std::string& strMessage, bilingual_str user_message = bilingual_str{});
/** Initialize shutdown state. This must be called before using either StartShutdown(), /** Initialize shutdown state. This must be called before using either StartShutdown(),
* AbortShutdown() or WaitForShutdown(). Calling ShutdownRequested() is always safe. * AbortShutdown() or WaitForShutdown(). Calling ShutdownRequested() is always safe.
*/ */
bool InitShutdownState(); bool InitShutdownState(std::atomic<int>& exit_status);
/** Request shutdown of the application. */ /** Request shutdown of the application. */
void StartShutdown(); void StartShutdown();

View file

@ -40,7 +40,7 @@ class AbortNodeTest(BitcoinTestFramework):
# Check that node0 aborted # Check that node0 aborted
self.log.info("Waiting for crash") self.log.info("Waiting for crash")
self.nodes[0].wait_until_stopped(timeout=5) self.nodes[0].wait_until_stopped(timeout=5, expect_error=True)
self.log.info("Node crashed - now verifying restart fails") self.log.info("Node crashed - now verifying restart fails")
self.nodes[0].assert_start_raises_init_error() self.nodes[0].assert_start_raises_init_error()

View file

@ -365,7 +365,7 @@ class TestNode():
if wait_until_stopped: if wait_until_stopped:
self.wait_until_stopped() self.wait_until_stopped()
def is_node_stopped(self): def is_node_stopped(self, expected_ret_code=None):
"""Checks whether the node has stopped. """Checks whether the node has stopped.
Returns True if the node has stopped. False otherwise. Returns True if the node has stopped. False otherwise.
@ -377,8 +377,13 @@ class TestNode():
return False return False
# process has stopped. Assert that it didn't return an error code. # process has stopped. Assert that it didn't return an error code.
assert return_code == 0, self._node_msg( # unless 'expected_ret_code' is provided.
"Node returned non-zero exit code (%d) when stopping" % return_code) if expected_ret_code is not None:
assert return_code == expected_ret_code, self._node_msg(
"Node returned unexpected exit code (%d) vs (%d) when stopping" % (return_code, expected_ret_code))
else:
assert return_code == 0, self._node_msg(
"Node returned non-zero exit code (%d) when stopping" % return_code)
self.running = False self.running = False
self.process = None self.process = None
self.rpc_connected = False self.rpc_connected = False
@ -386,8 +391,9 @@ class TestNode():
self.log.debug("Node stopped") self.log.debug("Node stopped")
return True return True
def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT): def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT, expect_error=False):
wait_until_helper(self.is_node_stopped, timeout=timeout, timeout_factor=self.timeout_factor) expected_ret_code = 1 if expect_error else None # Whether node shutdown return EXIT_FAILURE or EXIT_SUCCESS
wait_until_helper(lambda: self.is_node_stopped(expected_ret_code=expected_ret_code), timeout=timeout, timeout_factor=self.timeout_factor)
def replace_in_config(self, replacements): def replace_in_config(self, replacements):
""" """