Related issue #18643.
With this PR the GUI handles the wallet-related exception, displays it to a user:
and, if the exception is a non-fatal error, leaves the main window running.
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
Reviewers, 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.
Do I understand correctly that every gui element needs to have this handler if a crash wants to be avoided?
I.e. every dialog or window that could possibly pop up and just putting this catch in the “main gui” won’t work?
Do I understand correctly that every gui element needs to have this handler if a crash wants to be avoided?
I.e. every dialog or window that could possibly pop up and just putting this catch in the “main gui” won’t work?
Every slot.
See: https://doc.qt.io/qt-5/exceptionsafety.html#signals-and-slots
So currently it is undefined behaviour. Wow, qt is scary.
Slightly stronger concept ACK, but this also makes we want to remove qt.
So currently it is undefined behaviour. Wow, qt is scary.
Slightly stronger concept ACK, but this also makes we want to remove qt.
Unrelated, but do we have a long-term plan of moving from Qt?
448+{
449+ try {
450+ bumpFeeHelper();
451+ } catch (const NonFatalCheckError& e) {
452+ Q_EMIT nonFatalCheckError(QString::fromStdString(e.what()));
453+ }
No reason to tie this to to the CHECK_NONFATAL implementation or to have exceptions pass through QT code which by design does not use them. Would change this to not be tied to a specific exception type:
0try {
1 bumpFeeHelper();
2} catch (const std::exception& e) {
3 Q_EMIT runawayException(QString::fromStdString(e.what()));
4} catch (...) {
5 Q_EMIT runawayException("Unknown failure occurred");
6}
It looks like other code could stay the same except to renaming nonFatalCheckError
to runawayException
1100@@ -1101,6 +1101,11 @@ void BitcoinGUI::message(const QString& title, QString message, unsigned int sty
1101 }
1102 }
1103
1104+void BitcoinGUI::handleNonFatalCheckError(const QString& error)
1105+{
1106+ QMessageBox::warning(nullptr, "NonFatalCheckError exception", error);
Updated 40a622dd9374e8cba80c6150f56f9d43004d953a -> 44f7f519e5fc7aef6df12474ce3c9b8583e6f20b (pr18897.01 -> pr18897.02, diff):
The OP and the screenshot have been updated.
This looks pretty reasonable, and I’d ack this if not a draft PR.
One thing that might be nice is a generic exception catcher QObject that emits a handleRunawayException signal. This way the signal doesn’t need to be declared multiple places and repeated boilerplate for catching exceptions could go away. So something like:
0m_exception_catcher->exec([&]{ sendButtonClickedHelper(); });
could replace:
0try {
1 sendButtonClickedHelper();
2} catch (const std::exception& e) {
3 Q_EMIT runawayException(QString::fromStdString(e.what()));
4} catch (...) {
5 Q_EMIT runawayException(QString("Unknown failure occurred."));
6}
461@@ -461,6 +462,18 @@ void SendCoinsDialog::on_sendButton_clicked()
462 m_current_transaction.reset();
463 }
464
465+void SendCoinsDialog::on_sendButton_clicked()
466+{
467+ try {
468+ sendButtonClickedHelper();
469+ } catch (const std::exception& e) {
470+ Q_EMIT runawayException(QString::fromStdString(e.what()));
Why emit a signal? No one is interested in this signal right? Could just call another slot in a generic catcher, like:
0template <typename F> void shieldException(F&& f) {
1 try {
2 f();
3 } catch (...) {
4 QMetaObject::invokeMethod(qApp, "messageException", Qt::QueuedConnection,
5 Q_ARG(QString, exception_message), ...
6 );
7 }
8}
There’s this approach:
0bool BitcoinApplication::notify(QObject* receiver, QEvent* event)
1{
2 try {
3 return QApplication::notify(receiver, event);
4 } catch (const std::exception& ex) {
5 // ..
6 } catch (...) {
7 // ..
8 }
9 return false;
10}
But the documentation says this will be deprecated.
So currently it is undefined behaviour. Wow, qt is scary.
Slightly stronger concept ACK, but this also makes we want to remove qt.
Why? This is by design, Qt doesn’t want to handle exceptions, what should it do anyway?
@ryanofsky @promag Thank you for your reviews.
Updated 44f7f519e5fc7aef6df12474ce3c9b8583e6f20b -> 24a90d54ed31fd3da787b95e29e2fca581235836 (pr18897.02 -> pr18897.03, diff).
Ready for review now :)
12+#include <QMetaObject>
13+#include <QString>
14+
15+#include <cassert>
16+
17+template <typename FunctionType> void shieldException(FunctionType&& f) {
Updated 24a90d54ed31fd3da787b95e29e2fca581235836 -> 82529bc4ed82bcdf42428afef63c12ff37dd1dd4 (pr18897.03 -> pr18897.04, diff):
Move to src/qt/guiutil.h?
290+ bool ok = true;
291+ try {
292+ f();
293+ } catch (const NonFatalCheckError& e) {
294+ PrintExceptionContinue(&e, "Internal error");
295+ ok = QMetaObject::invokeMethod(qApp, "handleNonFatalException", Qt::DirectConnection, Q_ARG(QString, QString::fromStdString(e.what())));
Why can’t it be AutoConnection
? That way this could be used in a “worker” slot.
Otherwise I’d assert the current thread is the GUI thread at the function begin.
I’d keep Qt::DirectConnection
to be sure the exception is handled in the same thread where it was thrown.
As QMessageBox
in BitcoinApplication::handleNonFatalException()
has its own event loop, I think it is safe to call shieldException()
from a non-GUI thread, no?
has its own event loop, I think it is safe to call shieldException() from a non-GUI thread, no?
I don’t think so, only the main (GUI) thread should deal with widgets.
I’d keep
Qt::DirectConnection
to be sure the exception is handled in the same thread where it was thrown.
What you mean by handled? This is just catching the exception (which happens in the right thread) and then showing the message. If you want to block until the user presses OK then try GUIUtil::blockingGUIThreadConnection.
Throwing an exception from a slot invoked by Qt's signal-slot connection
mechanism is considered undefined behavior, unless it is handled within
the slot. The shieldException() function should be used for exception
handling within slots.
Updated 1fb68b3c6cd3d153a5701b5e82f18b1f6bf96586 -> 8f9424329f05ffa92a7421990c5c7320c7556a09 (pr18897.05 -> pr18897.06, diff):
I don’t think so, only the main (GUI) thread should deal with widgets.
288+ */
289+ template <typename FunctionType> void shieldException(FunctionType&& f) {
290+ bool ok = true;
291+ try {
292+ f();
293+ } catch (const NonFatalCheckError& e) {
In commit “qt: Add shieldException() function” (553128b91934a5e62c9827cd6260fec9562b5ec9)
Would drop this special case for NonFatalCheckError and simply catch std::exception below. NonFatalCheckError is really an internal implementation of the CHECK macro and most useful to unit tests. Exceptions aren’t valid in qt callbacks so NonFatalCheckError is no different here than any other exception. Just another unexpected condition that should never happen
292+ f();
293+ } catch (const NonFatalCheckError& e) {
294+ PrintExceptionContinue(&e, "Internal error");
295+ ok = QMetaObject::invokeMethod(qApp, "handleNonFatalException", blockingGUIThreadConnection(), Q_ARG(QString, QString::fromStdString(e.what())));
296+ } catch (const std::exception& e) {
297+ PrintExceptionContinue(&e, "Runaway exception");
In commit “qt: Add shieldException() function” (553128b91934a5e62c9827cd6260fec9562b5ec9)
Here and below would replace “Runaway exception” with “Internal error.” No reason to confuse users with implementation details and obscure jargon when it’s not providing useful information
Code review ACK 8f9424329f05ffa92a7421990c5c7320c7556a09. This looks very good and much simpler than before!
IMO it would be nice to have a followup PR that eliminated the one-line forwarding methods and just used shieldException directly at the call and signal connect sites. Alternately renaming s/Helper/Unsafe/ would be clearer than status quo. But best to just get rid of the indirections.
🐙 This pull request conflicts with the target branch and needs rebase.