-
Notifications
You must be signed in to change notification settings - Fork 37.7k
Get rid of shutdown.cpp/shutdown.h, use SignalInterrupt directly #28051
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code CoverageFor detailed information about the code coverage, see the test coverage report. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
I have the same question here: #27861 (comment) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept ACK
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Making this into a draft because I want to do more work to avoid the uncaught exceptions that Marco mentioned #27861 (comment), and address initialization order issues TheCharlatan mentioned #28051 (comment) and #28051 (comment)
Updated d9453bd -> 24169b1 (pr/noshut.2
-> pr/noshut.3
, compare) with EnsureAnyNodeContext suggestion
Concept ACK |
Removed draft state and rebased 0dce004 -> eccea15 ( |
Windows builds are currently unhappy: CXX kernel/libbitcoin_node_a-mempool_persist.o
init.cpp: In function ‘BOOL consoleCtrlHandler(DWORD)’:
init.cpp:386:15: error: ignoring return value of ‘bool util::SignalInterrupt::operator()()’, declared with attribute ‘nodiscard’ [-Werror=unused-result]
386 | g_shutdown();
| ~~~~~~~~~~^~
In file included from ./kernel/context.h:8,
from ./node/context.h:8,
from init.cpp:48:
./util/signalinterrupt.h:32:24: note: declared here
32 | [[nodiscard]] bool operator()();
| ^~~~~~~~
cc1plus: all warnings being treated as errors
make[2]: *** [Makefile:10167: libbitcoin_node_a-init.o] Error 1 |
…bal pointers Having InitContext() avoids the need to add duplicate code to src/init/*.cpp files in the next commit. It also lets these files avoid referencing global variables like gArgs. There is no change in behavior in this commit.
This change is mostly a refectoring that removes some code and gets rid of an unnecessary layer of indirection after bitcoin#27861 But it is not a pure refactoring since StartShutdown, AbortShutdown, and WaitForShutdown functions used to abort on failure, and the replacement code logs or returns errors instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rebased 437a0c2 -> 8a02ce5 (pr/noshut.18
-> pr/noshut.19
, compare) due to conflict with #28946
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 8a02ce5
sorry my answers to your questions are so long
I really appreciate the time you've taken to thoroughly address my comments. Thank you, it's extremely helpful.
#include <util/time.h> | ||
|
||
void IndexWaitSynced(const BaseIndex& index) | ||
void IndexWaitSynced(const BaseIndex& index, const util::SignalInterrupt& interrupt) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense, and I agree with your rationale, so I think my suggestion here in test/util/index.cpp
indeed should be disregarded. In node::AbortNode()
though, we are sending an interrupt signal to shutdown the node, so I still think it would make sense there? (non-blocking nit, ofc)
if (!(*Assert(node.shutdown))()) { | ||
LogPrintf("Error: failed to send shutdown signal after disk space check\n"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lower level code should just be returning errors and notifications, and high level code should be using that information to decide whether to shut down
That does seem like a better design, indeed. I agree then that having the bad approach be more verbose is not something to worry about, and my suggestion should be discarded. Thank you for providing the rationale, I think this might be useful to add to the PR description?
src/qt/bitcoin.cpp
Outdated
@@ -661,7 +661,7 @@ int GuiMain(int argc, char* argv[]) | |||
app.installEventFilter(new GUIUtil::LabelOutOfFocusEventFilter(&app)); | |||
#if defined(Q_OS_WIN) | |||
// Install global event filter for processing Windows session related Windows messages (WM_QUERYENDSESSION and WM_ENDSESSION) | |||
qApp->installNativeEventFilter(new WinShutdownMonitor()); | |||
qApp->installNativeEventFilter(new WinShutdownMonitor(app.node())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling app.node()
here before app.createNode
results in a crash on windows when running bitcoin-qt.exe
. I think the most straight forward way to resolve this would be to move this block of code to after createNode
is called.
Alternatively, to keep the current control flow, the way I read it, make_node
does not actually manipulate the node context, besides eventually setting it in the NodeImpl
further down the call stack. So I think the call to it could be moved further up:
diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp
index 8d4ca4ea92..a195db550f 100644
--- a/src/qt/bitcoin.cpp
+++ b/src/qt/bitcoin.cpp
@@ -301,0 +302,2 @@ void BitcoinApplication::createSplashScreen(const NetworkStyle *networkStyle)
+ assert(m_node);
+ m_splash->setNode(*m_node);
@@ -658,0 +661 @@ int GuiMain(int argc, char* argv[])
+ app.createNode(*init);
@@ -675,2 +677,0 @@ int GuiMain(int argc, char* argv[])
- app.createNode(*init);
Would be nice if we had tests that would exercise this code. Maybe a quick start and kill routine can be added to the windows native CI job?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re: #28051 (comment)
Calling
app.node()
here beforeapp.createNode
results in a crash on windows when runningbitcoin-qt.exe
.
Great catch! This should be fixed now by passing a shutdown callback and delaying the call to app.node(). I didn't want to move the createNode call, because the interfaces::Node object is intentionally created after the splash screen, so the splash screen can show up without a delay. The delay wouldn't be significant now, but with #10102 creating the node requires an IPC round trip, so might be noticeable.
Also agree with you it's a shame this part of the Qt codebase doesn't have CI test coverage. Your suggestion to add a CI step starting and stopping bitcoin-qt on windows (and probably all platforms bitcoin-qt is built for) would be a nice thing to implement. I think it would be most easily implemented as a python test.
Other ways to get more test coverage for this code might be to change one of the existing CI jobs to run existing python tests with bitcoin-qt
instead of bitcoind
. Or to refactor the GuiMain function and write unit tests for it. But unlike your suggestion, these approaches would be unlikely to trigger this particular bug since it is windows-specific.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the rationale, I indeed did not think of the future IPC delay. If we only start processing events after app.exec()
, why not move this to just before registerShutdownBlockReason
? Looking at the history it looks to me like this was put where it is to be in the general vicinity of the installEvent*
calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the rationale, I indeed did not think of the future IPC delay. If we only start processing events after
app.exec()
, why not move this to just beforeregisterShutdownBlockReason
? Looking at the history it looks to me like this was put where it is to be in the general vicinity of theinstallEvent*
calls.
I can make this change if you still think it's a good idea after this, but I think it's reasonable to want to keep the installEvent calls together. I also didn't want to just move the install event call when it's not necessary.
I think the current approach of passing a lambda argument to shutdownmonitor instead of passing a interfaces::Node
pointer like the previous approach is better in any case, because it makes it obvious that the only thing shutdownmonitor can do is call the startShutdown function, and that it is not going to interact with the node in some other way. It also makes the shutdown monitor class more future-proof so it the WM_QUERYENDSESSION / WM_ENDSESSION code shouldn't need to change when there are changes to the node interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated 8a02ce5 -> 4a3a265 (pr/noshut.19
-> pr/noshut.20
, compare) with Qt windows bugfix
src/qt/bitcoin.cpp
Outdated
@@ -661,7 +661,7 @@ int GuiMain(int argc, char* argv[]) | |||
app.installEventFilter(new GUIUtil::LabelOutOfFocusEventFilter(&app)); | |||
#if defined(Q_OS_WIN) | |||
// Install global event filter for processing Windows session related Windows messages (WM_QUERYENDSESSION and WM_ENDSESSION) | |||
qApp->installNativeEventFilter(new WinShutdownMonitor()); | |||
qApp->installNativeEventFilter(new WinShutdownMonitor(app.node())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re: #28051 (comment)
Calling
app.node()
here beforeapp.createNode
results in a crash on windows when runningbitcoin-qt.exe
.
Great catch! This should be fixed now by passing a shutdown callback and delaying the call to app.node(). I didn't want to move the createNode call, because the interfaces::Node object is intentionally created after the splash screen, so the splash screen can show up without a delay. The delay wouldn't be significant now, but with #10102 creating the node requires an IPC round trip, so might be noticeable.
Also agree with you it's a shame this part of the Qt codebase doesn't have CI test coverage. Your suggestion to add a CI step starting and stopping bitcoin-qt on windows (and probably all platforms bitcoin-qt is built for) would be a nice thing to implement. I think it would be most easily implemented as a python test.
Other ways to get more test coverage for this code might be to change one of the existing CI jobs to run existing python tests with bitcoin-qt
instead of bitcoind
. Or to refactor the GuiMain function and write unit tests for it. But unlike your suggestion, these approaches would be unlikely to trigger this particular bug since it is windows-specific.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-ACK 4a3a265
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated 4a3a265 -> eaf915d (pr/noshut.20
-> pr/noshut.22
, compare) just implementing a suggested change to rename
if (!(*Assert(node.shutdown))()) { | ||
LogPrintf("Error: failed to send shutdown signal after disk space check\n"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re: #28051 (comment)
Thank you for providing the rationale, I think this might be useful to add to the PR description?
Definitely, and the PR description was pretty bad so I rewrote it. This is mentioned now at the end.
#include <util/time.h> | ||
|
||
void IndexWaitSynced(const BaseIndex& index) | ||
void IndexWaitSynced(const BaseIndex& index, const util::SignalInterrupt& interrupt) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re: #28051 (comment)
In
node::AbortNode()
though, we are sending an interrupt signal to shutdown the node, so I still think it would make sense there? (non-blocking nit, ofc)
Sorry I missed this comment before, and you are right about AbortNode. I renamed interrupt to shutdown there now and in the KernelNotifications class as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-ACK eaf915d
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to explain why the behavior changes are needed. Otherwise,
concept ACK eaf915d 👇
Show signature
Signature:
untrusted comment: signature from minisign secret key on empty file; verify via: minisign -Vm "${path_to_any_empty_file}" -P RWTRmVTMeKV5noAMqVlsMugDDCyyTSbA3Re5AkUrhvLVln0tSaFWglOw -x "${path_to_this_whole_four_line_signature_blob}"
RUTRmVTMeKV5npGrKx1nqXCw5zeVHdtdYURB/KlyA/LMFgpNCs+SkW9a8N95d+U4AP1RJMi+krxU1A3Yux4bpwZNLvVBKy0wLgM=
trusted comment: concept ACK eaf915d61d470372e63f41f11d6a873c1936f73f 👇
Zj5llDu5414hpNoftK5OmsCqlMoeZXi2akcICzuiYesSYUrLhMj2dGrGP1Ibp7xziOqBbwqKkTbUNzoKgL5eBQ==
{ | ||
SetMiscWarning(Untranslated(debug_message)); | ||
LogPrintf("*** %s\n", debug_message); | ||
InitError(user_message.empty() ? _("A fatal internal error occurred, see debug.log for details") : user_message); | ||
exit_status.store(EXIT_FAILURE); | ||
if (shutdown) StartShutdown(); | ||
if (shutdown && !(*shutdown)()) { | ||
LogPrintf("Error: failed to send shutdown signal\n"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ae66429: Is it required to change the behavior here? Why not keep the abort?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re: #28051 (comment)
ae66429: Is it required to change the behavior here? Why not keep the abort?
In general, I don't think dumping core is a good way to handle errors. I think it can be a reasonable thing to do if inconsistent internal state is detected or if a precondition is violated, and halting right away will provide better debug information. Or it could make sense if it's likely that data corruption or other bad outcomes will happen if execution doesn't stop as quickly as possible.
But in this case when an ordinary system call that is not saving data returns an error code, I think it it is better to log the failure and move on than to crash the entire process abruptly when we don't know what other threads are running, what wallets might be loaded, what data might be lost, etc, as a result of an abort().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this code will never be hit, so it doesn't matter much. If it was hit, I don't see how the user would shutdown the process without an unclean termination, so might as well do an abort. Generally, it should be fine to abort Bitcoin Core at any time, because any critical data should always be flushed atomically to storage.
But as this code should never be hit, it also seems fine to give the user the option to call RPCs to unload wallets, flush the chainstate, and disconnect peers, etc, before doing the unclean termination.
@@ -62,7 +61,9 @@ kernel::InterruptResult KernelNotifications::blockTip(SynchronizationState state | |||
{ | |||
uiInterface.NotifyBlockTip(state, &index); | |||
if (m_stop_at_height && index.nHeight >= m_stop_at_height) { | |||
StartShutdown(); | |||
if (!m_shutdown()) { | |||
LogPrintf("Error: failed to send shutdown signal after reaching stop height\n"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/init.cpp
Outdated
// And then, WaitForShutdown() makes all other on-going threads | ||
// in the thread group join the main thread. | ||
// A clean exit happens when the SignalInterrupt object is triggered, which | ||
// makes main thread's SignalInterrupt::wait() call return, and join all other |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit in eaf915d: "the main thread"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/init.cpp
Outdated
@@ -222,6 +225,11 @@ void InitContext(NodeContext& node) | |||
// shutdown thing. | |||
// | |||
|
|||
bool ShutdownRequested(node::NodeContext& node) | |||
{ | |||
return bool{*(Assert(node.shutdown))}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit in eaf915d: Can drop the ()
around Assert()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated eaf915d -> 6db04be (pr/noshut.22
-> pr/noshut.23
, compare) just making suggested code tweaks and updating all commit messages to say what behavior is changing.
re: #28051 (review)
It would be good to explain why the behavior changes are needed.
Thank you, good catch. Some of the commits were explaining behavior changes, but not not all, and the PR description did not mention them. This should all be fixed now.
concept ACK eaf915d 👇
FYI, I noticed DrahtBot is interpreting this concept ack as a normal ACK in #28051 (comment), probably because "concept" is not capitalized
src/init.cpp
Outdated
// And then, WaitForShutdown() makes all other on-going threads | ||
// in the thread group join the main thread. | ||
// A clean exit happens when the SignalInterrupt object is triggered, which | ||
// makes main thread's SignalInterrupt::wait() call return, and join all other |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/init.cpp
Outdated
@@ -222,6 +225,11 @@ void InitContext(NodeContext& node) | |||
// shutdown thing. | |||
// | |||
|
|||
bool ShutdownRequested(node::NodeContext& node) | |||
{ | |||
return bool{*(Assert(node.shutdown))}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{ | ||
SetMiscWarning(Untranslated(debug_message)); | ||
LogPrintf("*** %s\n", debug_message); | ||
InitError(user_message.empty() ? _("A fatal internal error occurred, see debug.log for details") : user_message); | ||
exit_status.store(EXIT_FAILURE); | ||
if (shutdown) StartShutdown(); | ||
if (shutdown && !(*shutdown)()) { | ||
LogPrintf("Error: failed to send shutdown signal\n"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re: #28051 (comment)
ae66429: Is it required to change the behavior here? Why not keep the abort?
In general, I don't think dumping core is a good way to handle errors. I think it can be a reasonable thing to do if inconsistent internal state is detected or if a precondition is violated, and halting right away will provide better debug information. Or it could make sense if it's likely that data corruption or other bad outcomes will happen if execution doesn't stop as quickly as possible.
But in this case when an ordinary system call that is not saving data returns an error code, I think it it is better to log the failure and move on than to crash the entire process abruptly when we don't know what other threads are running, what wallets might be loaded, what data might be lost, etc, as a result of an abort().
@@ -62,7 +61,9 @@ kernel::InterruptResult KernelNotifications::blockTip(SynchronizationState state | |||
{ | |||
uiInterface.NotifyBlockTip(state, &index); | |||
if (m_stop_at_height && index.nHeight >= m_stop_at_height) { | |||
StartShutdown(); | |||
if (!m_shutdown()) { | |||
LogPrintf("Error: failed to send shutdown signal after reaching stop height\n"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-ACK 6db04be
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 6db04be 👗
Show signature
Signature:
untrusted comment: signature from minisign secret key on empty file; verify via: minisign -Vm "${path_to_any_empty_file}" -P RWTRmVTMeKV5noAMqVlsMugDDCyyTSbA3Re5AkUrhvLVln0tSaFWglOw -x "${path_to_this_whole_four_line_signature_blob}"
RUTRmVTMeKV5npGrKx1nqXCw5zeVHdtdYURB/KlyA/LMFgpNCs+SkW9a8N95d+U4AP1RJMi+krxU1A3Yux4bpwZNLvVBKy0wLgM=
trusted comment: ACK 6db04be102807ee0120981a9b8de62a55439dabb 👗
svhRtNkGG1s2bMv7s6ElM2CG7nlbJ6ersNj58rdTM6YsNu2LTGwUMhVYMVduUxOzUnE++tiWYBOB91ptoUk5Ag==
@@ -26,6 +26,11 @@ namespace node { | |||
struct NodeContext; | |||
} // namespace node | |||
|
|||
/** Initialize node context shutdown and args variables. */ | |||
void InitContext(node::NodeContext& node); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit in the last commit: Shouldn't new node code be in the node namespace? Otherwise it will have to be touched again when it is moved into the node namespace later?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re: #28051 (comment)
Nit in the last commit: Shouldn't new node code be in the node namespace? Otherwise it will have to be touched again when it is moved into the node namespace later?
This is true, but it is not different from other init.h methods. Logically init.h and init.cpp belong in node subdirectory and should use the node namespace, so I didn't want to make this one function stand out. The function also does need to be located in this file because it is accesses a static variable. So just keeping the Init functions in the same namespace seemed like the most straightforward approach for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re-ACK 6db04be
ACK 6db04be Code seems fine, but I'm not familiar enough with this area to say whether this is the right approach or if there are any side effects that haven't been thought of. |
@@ -18,24 +18,9 @@ namespace kernel { | |||
//! State stored directly in this struct should be simple. More complex state | |||
//! should be stored to std::unique_ptr members pointing to opaque types. | |||
struct Context { | |||
//! Interrupt object that can be used to stop long-running kernel operations. | |||
util::SignalInterrupt interrupt; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noticed while reviewing #29252...
This was removed, but the #include <util/signalinterrupt.h>
include wasn't. I can open a quick cleanup PR, or would you like to @ryanofsky ?
(Looks like <memory>
can go too.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Looks like
<memory>
can go too.)
The iwyu output from https://cirrus-ci.com/task/5795750523699200 / https://api.cirrus-ci.com/v1/task/5795750523699200/logs/ci.log :
kernel/context.h should add these lines:
kernel/context.h should remove these lines:
- #include <util/signalinterrupt.h> // lines 8-8
- #include <memory> // lines 10-10
The full include-list for kernel/context.h:
---
kernel/context.cpp should add these lines:
kernel/context.cpp should remove these lines:
- #include <pubkey.h> // lines 10-10
The full include-list for kernel/context.cpp:
#include <kernel/context.h>
#include <crypto/sha256.h> // for SHA256AutoDetect
#include <key.h> // for ECC_Start, ECC_Stop
#include <logging.h> // for LogPrintf_, LogPrintf
#include <random.h> // for RandomInit
#include <string> // for string
---
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. I don't think I would open a new PR that just removing an unnecessary #include without having a larger goal, but I wouldn't object to one and would happily review. Maybe the include fixes could be part of #29252, too.
This change drops
shutdown.h
andshutdown.cpp
files, replacing them with aNodeContext::shutdown
member which is used to trigger shutdowns directly. This gets rid of an unnecessary layer of indirection, and allows getting rid of thekernel::g_context
global.Additionally, this PR tries to improve error handling of
SignalInterrupt
code by marking relevant methods[[nodiscard]]
to avoid the possibility of uncaught exceptions mentioned #27861 (comment).Behavior is changing In a few cases which are noted in individual commit messages. Particularly: GUI code more consistently interrupts RPCs when it is shutting down, shutdown state no longer persists between unit tests, the stop RPC now returns an RPC error if requesting shutdown fails instead of aborting, and other failed shutdown calls now log errors instead of aborting.
This PR is a net reduction in lines of code, but in some cases the explicit error handling and lack of global shutdown functions do make it more verbose. The verbosity can be seen as good thing if it discourages more code from directly triggering shutdowns, and instead encourages code to return errors or send notifications that could be translated into shutdowns. Probably a number of existing shutdown calls could just be replaced by better error handling.