Skip to content

Commit 3b2c61e

Browse files
furszyryanofsky
andcommitted
Return EXIT_FAILURE on post-init fatal errors
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. Co-authored-by: Ryan Ofsky <[email protected]>
1 parent 3c06926 commit 3b2c61e

File tree

9 files changed

+32
-14
lines changed

9 files changed

+32
-14
lines changed

src/bitcoind.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ static bool AppInit(NodeContext& node, int argc, char* argv[])
169169
// Set this early so that parameter interactions go to console
170170
InitLogging(args);
171171
InitParameterInteraction(args);
172-
if (!AppInitBasicSetup(args)) {
172+
if (!AppInitBasicSetup(args, node.exit_status)) {
173173
// InitError will have been called with detailed error, which ends up on console
174174
return false;
175175
}
@@ -238,6 +238,8 @@ static bool AppInit(NodeContext& node, int argc, char* argv[])
238238
SetSyscallSandboxPolicy(SyscallSandboxPolicy::SHUTOFF);
239239
if (fRet) {
240240
WaitForShutdown();
241+
} else {
242+
node.exit_status = EXIT_FAILURE;
241243
}
242244
Interrupt(node);
243245
Shutdown(node);
@@ -264,5 +266,5 @@ MAIN_FUNCTION
264266
// Connect bitcoind signal handlers
265267
noui_connect();
266268

267-
return (AppInit(node, argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE);
269+
return AppInit(node, argc, argv) ? node.exit_status.load() : EXIT_FAILURE;
268270
}

src/init.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,7 @@ std::set<BlockFilterType> g_enabled_filter_types;
800800
std::terminate();
801801
};
802802

803-
bool AppInitBasicSetup(const ArgsManager& args)
803+
bool AppInitBasicSetup(const ArgsManager& args, std::atomic<int>& exit_status)
804804
{
805805
// ********************************************************* Step 1: setup
806806
#ifdef _MSC_VER
@@ -814,7 +814,7 @@ bool AppInitBasicSetup(const ArgsManager& args)
814814
// Enable heap terminate-on-corruption
815815
HeapSetInformation(nullptr, HeapEnableTerminationOnCorruption, nullptr, 0);
816816
#endif
817-
if (!InitShutdownState()) {
817+
if (!InitShutdownState(exit_status)) {
818818
return InitError(Untranslated("Initializing wait-for-shutdown state failed."));
819819
}
820820

src/init.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ void InitParameterInteraction(ArgsManager& args);
3838
* @note This can be done before daemonization. Do not call Shutdown() if this function fails.
3939
* @pre Parameters should be parsed and config file should be read.
4040
*/
41-
bool AppInitBasicSetup(const ArgsManager& args);
41+
bool AppInitBasicSetup(const ArgsManager& args, std::atomic<int>& exit_status);
4242
/**
4343
* Initialization: parameter interaction.
4444
* @note This can be done before daemonization. Do not call Shutdown() if this function fails.

src/node/context.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88
#include <kernel/context.h>
99

10+
#include <atomic>
1011
#include <cassert>
12+
#include <cstdlib>
1113
#include <functional>
1214
#include <memory>
1315
#include <vector>
@@ -65,6 +67,7 @@ struct NodeContext {
6567
std::unique_ptr<CScheduler> scheduler;
6668
std::function<void()> rpc_interruption_point = [] {};
6769
std::unique_ptr<KernelNotifications> notifications;
70+
std::atomic<int> exit_status{EXIT_SUCCESS};
6871

6972
//! Declare default constructor and destructor that are not inline, so code
7073
//! instantiating the NodeContext struct doesn't need to #include class

src/node/interfaces.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class NodeImpl : public Node
9292
uint32_t getLogCategories() override { return LogInstance().GetCategoryMask(); }
9393
bool baseInitialize() override
9494
{
95-
if (!AppInitBasicSetup(args())) return false;
95+
if (!AppInitBasicSetup(args(), Assert(context())->exit_status)) return false;
9696
if (!AppInitParameterInteraction(args(), /*use_syscall_sandbox=*/false)) return false;
9797

9898
m_context->kernel = std::make_unique<kernel::Context>();

src/shutdown.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include <logging.h>
1313
#include <node/interface_ui.h>
14+
#include <util/check.h>
1415
#include <util/tokenpipe.h>
1516
#include <warnings.h>
1617

@@ -20,6 +21,8 @@
2021
#include <condition_variable>
2122
#endif
2223

24+
static std::atomic<int>* g_exit_status{nullptr};
25+
2326
bool AbortNode(const std::string& strMessage, bilingual_str user_message)
2427
{
2528
SetMiscWarning(Untranslated(strMessage));
@@ -28,6 +31,7 @@ bool AbortNode(const std::string& strMessage, bilingual_str user_message)
2831
user_message = _("A fatal internal error occurred, see debug.log for details");
2932
}
3033
InitError(user_message);
34+
Assert(g_exit_status)->store(EXIT_FAILURE);
3135
StartShutdown();
3236
return false;
3337
}
@@ -44,8 +48,9 @@ static TokenPipeEnd g_shutdown_r;
4448
static TokenPipeEnd g_shutdown_w;
4549
#endif
4650

47-
bool InitShutdownState()
51+
bool InitShutdownState(std::atomic<int>& exit_status)
4852
{
53+
g_exit_status = &exit_status;
4954
#ifndef WIN32
5055
std::optional<TokenPipe> pipe = TokenPipe::Make();
5156
if (!pipe) return false;

src/shutdown.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88

99
#include <util/translation.h> // For bilingual_str
1010

11+
#include <atomic>
12+
1113
/** Abort with a message */
1214
bool AbortNode(const std::string& strMessage, bilingual_str user_message = bilingual_str{});
1315

1416
/** Initialize shutdown state. This must be called before using either StartShutdown(),
1517
* AbortShutdown() or WaitForShutdown(). Calling ShutdownRequested() is always safe.
1618
*/
17-
bool InitShutdownState();
19+
bool InitShutdownState(std::atomic<int>& exit_status);
1820

1921
/** Request shutdown of the application. */
2022
void StartShutdown();

test/functional/feature_abortnode.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def run_test(self):
4040

4141
# Check that node0 aborted
4242
self.log.info("Waiting for crash")
43-
self.nodes[0].wait_until_stopped(timeout=5)
43+
self.nodes[0].wait_until_stopped(timeout=5, expect_error=True)
4444
self.log.info("Node crashed - now verifying restart fails")
4545
self.nodes[0].assert_start_raises_init_error()
4646

test/functional/test_framework/test_node.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ def stop_node(self, expected_stderr='', *, wait=0, wait_until_stopped=True):
365365
if wait_until_stopped:
366366
self.wait_until_stopped()
367367

368-
def is_node_stopped(self):
368+
def is_node_stopped(self, expected_ret_code=None):
369369
"""Checks whether the node has stopped.
370370
371371
Returns True if the node has stopped. False otherwise.
@@ -377,17 +377,23 @@ def is_node_stopped(self):
377377
return False
378378

379379
# process has stopped. Assert that it didn't return an error code.
380-
assert return_code == 0, self._node_msg(
381-
"Node returned non-zero exit code (%d) when stopping" % return_code)
380+
# unless 'expected_ret_code' is provided.
381+
if expected_ret_code is not None:
382+
assert return_code == expected_ret_code, self._node_msg(
383+
"Node returned unexpected exit code (%d) vs (%d) when stopping" % (return_code, expected_ret_code))
384+
else:
385+
assert return_code == 0, self._node_msg(
386+
"Node returned non-zero exit code (%d) when stopping" % return_code)
382387
self.running = False
383388
self.process = None
384389
self.rpc_connected = False
385390
self.rpc = None
386391
self.log.debug("Node stopped")
387392
return True
388393

389-
def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT):
390-
wait_until_helper(self.is_node_stopped, timeout=timeout, timeout_factor=self.timeout_factor)
394+
def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT, expect_error=False):
395+
expected_ret_code = 1 if expect_error else None # Whether node shutdown return EXIT_FAILURE or EXIT_SUCCESS
396+
wait_until_helper(lambda: self.is_node_stopped(expected_ret_code=expected_ret_code), timeout=timeout, timeout_factor=self.timeout_factor)
391397

392398
def replace_in_config(self, replacements):
393399
"""

0 commit comments

Comments
 (0)