From 202d853bbea8ecb2164b9c9dc69e2129b45f05f8 Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Tue, 7 Jan 2014 11:30:17 +0100 Subject: [PATCH] qt: Move initialization/shutdown to a thread Move AppInit2 and Shutdown to a thread. This allows a more responsive splash screen, prevents 'process does not respond' messages from the window system and will allow for showing a user friendly window while shutting down. --- src/qt/Makefile.am | 2 +- src/qt/bitcoin.cpp | 411 +++++++++++++++++++++++++++++----------- src/qt/splashscreen.cpp | 2 + 3 files changed, 301 insertions(+), 114 deletions(-) diff --git a/src/qt/Makefile.am b/src/qt/Makefile.am index abd8a9f11..99fdecb03 100644 --- a/src/qt/Makefile.am +++ b/src/qt/Makefile.am @@ -108,7 +108,7 @@ QT_MOC_CPP = moc_aboutdialog.cpp moc_addressbookpage.cpp \ BITCOIN_MM = macdockiconhandler.mm macnotificationhandler.mm -QT_MOC = intro.moc overviewpage.moc rpcconsole.moc +QT_MOC = intro.moc overviewpage.moc rpcconsole.moc bitcoin.moc QT_QRC_CPP = qrc_bitcoin.cpp QT_QRC = bitcoin.qrc diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 794d12f69..d4a834acb 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -30,6 +30,8 @@ #include #include #include +#include +#include #if defined(QT_STATICPLUGIN) #include @@ -52,18 +54,18 @@ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); // Declare meta types used for QMetaObject::invokeMethod Q_DECLARE_METATYPE(bool*) -// Need a global reference for the notifications to find the GUI -static BitcoinGUI *guiref; -static SplashScreen *splashref; +// Need a global reference for the notifications to find the GUI and splash screen +static QWeakPointer guiref; +static QWeakPointer splashref; static bool ThreadSafeMessageBox(const std::string& message, const std::string& caption, unsigned int style) { - if(guiref) + if(!guiref.isNull()) { bool modal = (style & CClientUIInterface::MODAL); bool ret = false; // In case of modal message, use blocking connection to wait for user to click a button - QMetaObject::invokeMethod(guiref, "message", + QMetaObject::invokeMethod(guiref.data(), "message", modal ? GUIUtil::blockingGUIThreadConnection() : Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(caption)), Q_ARG(QString, QString::fromStdString(message)), @@ -81,10 +83,13 @@ static bool ThreadSafeMessageBox(const std::string& message, const std::string& static void InitMessage(const std::string &message) { - if(splashref) + if(!splashref.isNull()) { - splashref->showMessage(QString::fromStdString(message), Qt::AlignBottom|Qt::AlignHCenter, QColor(55,55,55)); - qApp->processEvents(); + QMetaObject::invokeMethod(splashref.data(), "showMessage", + Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(message)), + Q_ARG(int, Qt::AlignBottom|Qt::AlignHCenter), + Q_ARG(QColor, QColor(55,55,55))); } LogPrintf("init message: %s\n", message.c_str()); } @@ -97,15 +102,6 @@ static std::string Translate(const char* psz) return QCoreApplication::translate("bitcoin-core", psz).toStdString(); } -/* Handle runaway exceptions. Shows a message box with the problem and quits the program. - */ -static void handleRunawayException(std::exception *e) -{ - PrintExceptionContinue(e, "Runaway exception"); - QMessageBox::critical(0, "Runaway exception", BitcoinGUI::tr("A fatal error occurred. Bitcoin can no longer continue safely and will quit.") + QString("\n\n") + QString::fromStdString(strMiscWarning)); - exit(1); -} - /** Set up translations */ static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTranslator, QTranslator &translatorBase, QTranslator &translator) { @@ -162,6 +158,265 @@ void DebugMessageHandler(QtMsgType type, const QMessageLogContext& context, cons } #endif +/** Class encapsulating Bitcoin Core startup and shutdown. + * Allows running startup and shutdown in a different thread from the UI thread. + */ +class BitcoinCore: public QObject +{ + Q_OBJECT +public: + explicit BitcoinCore(); + +public slots: + void initialize(); + void shutdown(); + +signals: + void initializeResult(int retval); + void shutdownResult(int retval); + void runawayException(const QString &message); + +private: + boost::thread_group threadGroup; + + /// Pass fatal exception message to UI thread + void handleRunawayException(std::exception *e); +}; + +/** Main Bitcoin application object */ +class BitcoinApplication: public QApplication +{ + Q_OBJECT +public: + explicit BitcoinApplication(int &argc, char **argv); + ~BitcoinApplication(); + + /// Create payment server + void createPaymentServer(); + /// Create options model + void createOptionsModel(); + /// Create main window + void createWindow(bool isaTestNet); + + /// Request core initialization + void requestInitialize(); + /// Request core shutdown + void requestShutdown(); + + /// Get process return value + int getReturnValue() { return returnValue; } + +public slots: + void initializeResult(int retval); + void shutdownResult(int retval); + /// Handle runaway exceptions. Shows a message box with the problem and quits the program. + void handleRunawayException(const QString &message); + +signals: + void requestedInitialize(); + void requestedShutdown(); + void stopThread(); + +private: + QThread *coreThread; + PaymentServer* paymentServer; + OptionsModel *optionsModel; + ClientModel *clientModel; + BitcoinGUI *window; + WalletModel *walletModel; + int returnValue; + + void startThread(); +}; + +#include "bitcoin.moc" + +BitcoinCore::BitcoinCore(): + QObject() +{ +} + +void BitcoinCore::handleRunawayException(std::exception *e) +{ + PrintExceptionContinue(e, "Runaway exception"); + emit runawayException(QString::fromStdString(strMiscWarning)); +} + +void BitcoinCore::initialize() +{ + try + { + LogPrintf("Running AppInit2 in thread\n"); + int rv = AppInit2(threadGroup); + emit initializeResult(rv); + } catch (std::exception& e) { + handleRunawayException(&e); + } catch (...) { + handleRunawayException(NULL); + } +} + +void BitcoinCore::shutdown() +{ + try + { + LogPrintf("Running Shutdown in thread\n"); + threadGroup.interrupt_all(); + threadGroup.join_all(); + Shutdown(); + LogPrintf("Shutdown finished\n"); + emit shutdownResult(1); + } catch (std::exception& e) { + handleRunawayException(&e); + } catch (...) { + handleRunawayException(NULL); + } +} + +BitcoinApplication::BitcoinApplication(int &argc, char **argv): + QApplication(argc, argv), + coreThread(0), + paymentServer(0), + optionsModel(0), + clientModel(0), + window(0), + walletModel(0), + returnValue(0) +{ + setQuitOnLastWindowClosed(false); + startThread(); +} + +BitcoinApplication::~BitcoinApplication() +{ + LogPrintf("Stopping thread\n"); + emit stopThread(); + coreThread->wait(); + LogPrintf("Stopped thread\n"); +} + +void BitcoinApplication::createPaymentServer() +{ + paymentServer = new PaymentServer(this); +} + +void BitcoinApplication::createOptionsModel() +{ + optionsModel = new OptionsModel(); +} + +void BitcoinApplication::createWindow(bool isaTestNet) +{ + window = new BitcoinGUI(isaTestNet, 0); + guiref = window; + + QTimer* pollShutdownTimer = new QTimer(window); + connect(pollShutdownTimer, SIGNAL(timeout()), window, SLOT(detectShutdown())); + pollShutdownTimer->start(200); +} + +void BitcoinApplication::startThread() +{ + coreThread = new QThread(this); + BitcoinCore *executor = new BitcoinCore(); + executor->moveToThread(coreThread); + + /* communication to and from thread */ + connect(executor, SIGNAL(initializeResult(int)), this, SLOT(initializeResult(int))); + connect(executor, SIGNAL(shutdownResult(int)), this, SLOT(shutdownResult(int))); + connect(executor, SIGNAL(runawayException(QString)), this, SLOT(handleRunawayException(QString))); + connect(this, SIGNAL(requestedInitialize()), executor, SLOT(initialize())); + connect(this, SIGNAL(requestedShutdown()), executor, SLOT(shutdown())); + /* make sure executor object is deleted in its own thread */ + connect(this, SIGNAL(stopThread()), executor, SLOT(deleteLater())); + connect(this, SIGNAL(stopThread()), coreThread, SLOT(quit())); + + coreThread->start(); +} + +void BitcoinApplication::requestInitialize() +{ + LogPrintf("Requesting initialize\n"); + emit requestedInitialize(); +} + +void BitcoinApplication::requestShutdown() +{ + LogPrintf("Requesting shutdown\n"); + window->hide(); + window->setClientModel(0); + window->removeAllWallets(); + guiref.clear(); + delete walletModel; + emit requestedShutdown(); +} + +void BitcoinApplication::initializeResult(int retval) +{ + LogPrintf("Initialization result: %i\n", retval); + /// Set exit result: 0 if successful, 1 if failure + returnValue = retval ? 0 : 1; + if(retval) + { + optionsModel->Upgrade(); // Must be done after AppInit2 + + PaymentServer::LoadRootCAs(); + paymentServer->setOptionsModel(optionsModel); + + if (!splashref.isNull()) + splashref.data()->finish(window); + + clientModel = new ClientModel(optionsModel); + window->setClientModel(clientModel); + + if(pwalletMain) + { + walletModel = new WalletModel(pwalletMain, optionsModel); + + window->addWallet("~Default", walletModel); + window->setCurrentWallet("~Default"); + + connect(walletModel, SIGNAL(coinsSent(CWallet*,SendCoinsRecipient,QByteArray)), + paymentServer, SLOT(fetchPaymentACK(CWallet*,const SendCoinsRecipient&,QByteArray))); + } + + // If -min option passed, start window minimized. + if(GetBoolArg("-min", false)) + { + window->showMinimized(); + } + else + { + window->show(); + } + + // Now that initialization/startup is done, process any command-line + // bitcoin: URIs or payment requests: + connect(paymentServer, SIGNAL(receivedPaymentRequest(SendCoinsRecipient)), + window, SLOT(handlePaymentRequest(SendCoinsRecipient))); + connect(window, SIGNAL(receivedURI(QString)), + paymentServer, SLOT(handleURIOrFile(QString))); + connect(paymentServer, SIGNAL(message(QString,QString,unsigned int)), + window, SLOT(message(QString,QString,unsigned int))); + QTimer::singleShot(100, paymentServer, SLOT(uiReady())); + + } else { + quit(); // Exit main loop + } +} + +void BitcoinApplication::shutdownResult(int retval) +{ + LogPrintf("Shutdown result: %i\n", retval); + quit(); // Exit main loop after shutdown finished +} + +void BitcoinApplication::handleRunawayException(const QString &message) +{ + QMessageBox::critical(0, "Runaway exception", BitcoinGUI::tr("A fatal error occurred. Bitcoin can no longer continue safely and will quit.") + QString("\n\n") + message); + ::exit(1); +} + #ifndef BITCOIN_QT_TEST int main(int argc, char *argv[]) { @@ -189,7 +444,7 @@ int main(int argc, char *argv[]) #endif Q_INIT_RESOURCE(bitcoin); - QApplication app(argc, argv); + BitcoinApplication app(argc, argv); #if QT_VERSION > 0x050100 // Generate high-dpi pixmaps QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); @@ -254,7 +509,7 @@ int main(int argc, char *argv[]) // Start up the payment server early, too, so impatient users that click on // bitcoin: links repeatedly have their payment requests routed to this process: - PaymentServer* paymentServer = new PaymentServer(&app); + app.createPaymentServer(); /// 8. Main GUI initialization // Install global event filter that makes sure that long tooltips can be word-wrapped @@ -266,24 +521,29 @@ int main(int argc, char *argv[]) qInstallMessageHandler(DebugMessageHandler); #endif // Load GUI settings from QSettings - OptionsModel optionsModel; + app.createOptionsModel(); // Subscribe to global signals from core uiInterface.ThreadSafeMessageBox.connect(ThreadSafeMessageBox); uiInterface.InitMessage.connect(InitMessage); uiInterface.Translate.connect(Translate); - // Show splash screen if appropriate - SplashScreen splash(QPixmap(), 0, isaTestNet); - if (GetBoolArg("-splash", true) && !GetBoolArg("-min", false)) + // Show help message immediately after parsing command-line options (for "-lang") and setting locale, + // but before showing splash screen. + if (mapArgs.count("-?") || mapArgs.count("--help")) { - splash.show(); - splash.setAutoFillBackground(true); - splashref = &splash; + GUIUtil::HelpMessageBox help; + help.showOrPrint(); + return 1; } - app.processEvents(); - app.setQuitOnLastWindowClosed(false); + if (GetBoolArg("-splash", true) && !GetBoolArg("-min", false)) + { + SplashScreen *splash = new SplashScreen(QPixmap(), 0, isaTestNet); + splash->setAttribute(Qt::WA_DeleteOnClose); + splash->show(); + splashref = splash; + } try { @@ -293,93 +553,18 @@ int main(int argc, char *argv[]) if (GUIUtil::GetStartOnSystemStartup()) GUIUtil::SetStartOnSystemStartup(true); #endif - - boost::thread_group threadGroup; - - BitcoinGUI window(isaTestNet, 0); - guiref = &window; - - QTimer* pollShutdownTimer = new QTimer(guiref); - QObject::connect(pollShutdownTimer, SIGNAL(timeout()), guiref, SLOT(detectShutdown())); - pollShutdownTimer->start(200); - - if(AppInit2(threadGroup)) - { - { - // Put this in a block, so that the Model objects are cleaned up before - // calling Shutdown(). - - optionsModel.Upgrade(); // Must be done after AppInit2 - - PaymentServer::LoadRootCAs(); - paymentServer->setOptionsModel(&optionsModel); - - if (splashref) - splash.finish(&window); - - ClientModel clientModel(&optionsModel); - window.setClientModel(&clientModel); - - WalletModel *walletModel = 0; - if(pwalletMain) - walletModel = new WalletModel(pwalletMain, &optionsModel); - - if(walletModel) - { - window.addWallet("~Default", walletModel); - window.setCurrentWallet("~Default"); - } - - // If -min option passed, start window minimized. - if(GetBoolArg("-min", false)) - { - window.showMinimized(); - } - else - { - window.show(); - } - - // Now that initialization/startup is done, process any command-line - // bitcoin: URIs or payment requests: - QObject::connect(paymentServer, SIGNAL(receivedPaymentRequest(SendCoinsRecipient)), - &window, SLOT(handlePaymentRequest(SendCoinsRecipient))); - QObject::connect(&window, SIGNAL(receivedURI(QString)), - paymentServer, SLOT(handleURIOrFile(QString))); - if(walletModel) - { - QObject::connect(walletModel, SIGNAL(coinsSent(CWallet*,SendCoinsRecipient,QByteArray)), - paymentServer, SLOT(fetchPaymentACK(CWallet*,const SendCoinsRecipient&,QByteArray))); - } - QObject::connect(paymentServer, SIGNAL(message(QString,QString,unsigned int)), - guiref, SLOT(message(QString,QString,unsigned int))); - QTimer::singleShot(100, paymentServer, SLOT(uiReady())); - - app.exec(); - - window.hide(); - window.setClientModel(0); - window.removeAllWallets(); - guiref = 0; - delete walletModel; - } - // Shutdown the core and its threads, but don't exit the GUI here - threadGroup.interrupt_all(); - threadGroup.join_all(); - Shutdown(); - } - else - { - threadGroup.interrupt_all(); - threadGroup.join_all(); - Shutdown(); - return 1; - } + app.createWindow(isaTestNet); + app.requestInitialize(); + app.exec(); + app.requestShutdown(); + app.exec(); } catch (std::exception& e) { - handleRunawayException(&e); + PrintExceptionContinue(&e, "Runaway exception"); + app.handleRunawayException(QString::fromStdString(strMiscWarning)); } catch (...) { - handleRunawayException(NULL); + PrintExceptionContinue(NULL, "Runaway exception"); + app.handleRunawayException(QString::fromStdString(strMiscWarning)); } - return 0; + return app.getReturnValue(); } #endif // BITCOIN_QT_TEST diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp index 6fb834c04..0e998c6d7 100644 --- a/src/qt/splashscreen.cpp +++ b/src/qt/splashscreen.cpp @@ -13,6 +13,8 @@ SplashScreen::SplashScreen(const QPixmap &pixmap, Qt::WindowFlags f, bool isTestNet) : QSplashScreen(pixmap, f) { + setAutoFillBackground(true); + // set reference point, paddings int paddingRight = 50; int paddingTop = 50;