From be7810c5a877898d792cc7d4406dc05c3280b38f Mon Sep 17 00:00:00 2001 From: xiphon Date: Thu, 20 Jun 2019 20:28:59 +0000 Subject: [PATCH] qt: implement FutureScheduler, always await async code to complete --- monero-wallet-gui.pro | 2 + src/daemon/DaemonManager.cpp | 28 ++--- src/daemon/DaemonManager.h | 4 + src/libwalletqt/Wallet.cpp | 171 ++++++++++-------------------- src/libwalletqt/Wallet.h | 12 ++- src/libwalletqt/WalletManager.cpp | 71 ++++--------- src/libwalletqt/WalletManager.h | 8 +- src/qt/FutureScheduler.cpp | 89 ++++++++++++++++ src/qt/FutureScheduler.h | 79 ++++++++++++++ 9 files changed, 277 insertions(+), 187 deletions(-) create mode 100644 src/qt/FutureScheduler.cpp create mode 100644 src/qt/FutureScheduler.h diff --git a/monero-wallet-gui.pro b/monero-wallet-gui.pro index 8c4707c1..19eb5cde 100644 --- a/monero-wallet-gui.pro +++ b/monero-wallet-gui.pro @@ -63,6 +63,7 @@ HEADERS += \ src/libwalletqt/UnsignedTransaction.h \ Logger.h \ MainApp.h \ + src/qt/FutureScheduler.h \ src/qt/ipc.h \ src/qt/mime.h \ src/qt/KeysFiles.h \ @@ -96,6 +97,7 @@ SOURCES += main.cpp \ src/libwalletqt/UnsignedTransaction.cpp \ Logger.cpp \ MainApp.cpp \ + src/qt/FutureScheduler.cpp \ src/qt/ipc.cpp \ src/qt/mime.cpp \ src/qt/KeysFiles.cpp \ diff --git a/src/daemon/DaemonManager.cpp b/src/daemon/DaemonManager.cpp index 136e72bd..ecbe1784 100644 --- a/src/daemon/DaemonManager.cpp +++ b/src/daemon/DaemonManager.cpp @@ -131,19 +131,12 @@ bool DaemonManager::start(const QString &flags, NetworkType::Type nettype, const } // Start start watcher - QFuture future = QtConcurrent::run(this, &DaemonManager::startWatcher, nettype); - QFutureWatcher * watcher = new QFutureWatcher(); - connect(watcher, &QFutureWatcher::finished, - this, [this, watcher]() { - QFuture future = watcher->future(); - watcher->deleteLater(); - if(future.result()) + m_scheduler.run([this, nettype] { + if (startWatcher(nettype)) emit daemonStarted(); else emit daemonStartFailure(); }); - watcher->setFuture(future); - return true; } @@ -155,17 +148,12 @@ bool DaemonManager::stop(NetworkType::Type nettype) qDebug() << message; // Start stop watcher - Will kill if not shutting down - QFuture future = QtConcurrent::run(this, &DaemonManager::stopWatcher, nettype); - QFutureWatcher * watcher = new QFutureWatcher(); - connect(watcher, &QFutureWatcher::finished, - this, [this, watcher]() { - QFuture future = watcher->future(); - watcher->deleteLater(); - if(future.result()) { + m_scheduler.run([this, nettype] { + if (stopWatcher(nettype)) + { emit daemonStopped(); } }); - watcher->setFuture(future); return true; } @@ -330,6 +318,7 @@ QVariantMap DaemonManager::validateDataDir(const QString &dataDir) const DaemonManager::DaemonManager(QObject *parent) : QObject(parent) + , m_scheduler(this) { // Platform depetent path to monerod @@ -344,3 +333,8 @@ DaemonManager::DaemonManager(QObject *parent) m_has_daemon = false; } } + +DaemonManager::~DaemonManager() +{ + m_scheduler.shutdownWaitForFinished(); +} diff --git a/src/daemon/DaemonManager.h b/src/daemon/DaemonManager.h index e85152ba..6063c8cb 100644 --- a/src/daemon/DaemonManager.h +++ b/src/daemon/DaemonManager.h @@ -33,6 +33,7 @@ #include #include #include +#include "qt/FutureScheduler.h" #include "NetworkType.h" class DaemonManager : public QObject @@ -71,6 +72,8 @@ public slots: private: explicit DaemonManager(QObject *parent = 0); + ~DaemonManager(); + static DaemonManager * m_instance; static QStringList m_clArgs; QProcess *m_daemon; @@ -79,6 +82,7 @@ private: bool m_has_daemon = true; bool m_app_exit = false; + FutureScheduler m_scheduler; }; #endif // DAEMONMANAGER_H diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index ff0ada14..46f843de 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -150,13 +150,8 @@ NetworkType::Type Wallet::nettype() const void Wallet::updateConnectionStatusAsync() { - QFuture future = QtConcurrent::run(m_walletImpl, &Monero::Wallet::connected); - QFutureWatcher *connectionWatcher = new QFutureWatcher(); - - connect(connectionWatcher, &QFutureWatcher::finished, [=]() { - QFuture future = connectionWatcher->future(); - connectionWatcher->deleteLater(); - ConnectionStatus newStatus = static_cast(future.result()); + m_scheduler.run([this] { + ConnectionStatus newStatus = static_cast(m_walletImpl->connected()); if (newStatus != m_connectionStatus || !m_initialized) { m_initialized = true; m_connectionStatus = newStatus; @@ -166,7 +161,6 @@ void Wallet::updateConnectionStatusAsync() // Release lock m_connectionStatusRunning = false; }); - connectionWatcher->setFuture(future); } Wallet::ConnectionStatus Wallet::connected(bool forceCheck) @@ -246,15 +240,10 @@ void Wallet::initAsync(const QString &daemonAddress, quint64 upperTransactionLim emit connectionStatusChanged(m_connectionStatus); } - QFuture future = QtConcurrent::run(this, &Wallet::init, - daemonAddress, upperTransactionLimit, isRecovering, isRecoveringFromDevice, restoreHeight); - QFutureWatcher * watcher = new QFutureWatcher(); - - connect(watcher, &QFutureWatcher::finished, - this, [this, watcher, daemonAddress, upperTransactionLimit, isRecovering, restoreHeight]() { - QFuture future = watcher->future(); - watcher->deleteLater(); - if(future.result()){ + m_scheduler.run([this, daemonAddress, upperTransactionLimit, isRecovering, isRecoveringFromDevice, restoreHeight] { + bool success = init(daemonAddress, upperTransactionLimit, isRecovering, isRecoveringFromDevice, restoreHeight); + if (success) + { emit walletCreationHeightChanged(); qDebug() << "init async finished - starting refresh"; connected(true); @@ -262,7 +251,6 @@ void Wallet::initAsync(const QString &daemonAddress, quint64 upperTransactionLim } }); - watcher->setFuture(future); } //! create a view only wallet @@ -348,16 +336,32 @@ void Wallet::setSubaddressLabel(quint32 accountIndex, quint32 addressIndex, cons m_walletImpl->setSubaddressLabel(accountIndex, addressIndex, label.toStdString()); } -void Wallet::refreshHeightAsync() const +void Wallet::refreshHeightAsync() { - QtConcurrent::run([this] { - QFuture daemonHeight = QtConcurrent::run([this] { - return daemonBlockChainHeight(); + m_scheduler.run([this] { + quint64 daemonHeight; + QPair> daemonHeightFuture = m_scheduler.run([this, &daemonHeight] { + daemonHeight = daemonBlockChainHeight(); }); - QFuture targetHeight = QtConcurrent::run([this] { - return daemonBlockChainTargetHeight(); + if (!daemonHeightFuture.first) + { + return; + } + + quint64 targetHeight; + QPair> targetHeightFuture = m_scheduler.run([this, &targetHeight] { + targetHeight = daemonBlockChainTargetHeight(); }); - emit heightRefreshed(blockChainHeight(), daemonHeight.result(), targetHeight.result()); + if (!targetHeightFuture.first) + { + return; + } + + quint64 walletHeight = blockChainHeight(); + daemonHeightFuture.second.waitForFinished(); + targetHeightFuture.second.waitForFinished(); + + emit heightRefreshed(walletHeight, daemonHeight, targetHeight); }); } @@ -458,17 +462,10 @@ void Wallet::createTransactionAsync(const QString &dst_addr, const QString &paym quint64 amount, quint32 mixin_count, PendingTransaction::Priority priority) { - QFuture future = QtConcurrent::run(this, &Wallet::createTransaction, - dst_addr, payment_id,amount, mixin_count, priority); - QFutureWatcher * watcher = new QFutureWatcher(); - - connect(watcher, &QFutureWatcher::finished, - this, [this, watcher,dst_addr,payment_id,mixin_count]() { - QFuture future = watcher->future(); - watcher->deleteLater(); - emit transactionCreated(future.result(),dst_addr,payment_id,mixin_count); + m_scheduler.run([this, dst_addr, payment_id, amount, mixin_count, priority] { + PendingTransaction *tx = createTransaction(dst_addr, payment_id, amount, mixin_count, priority); + emit transactionCreated(tx, dst_addr, payment_id, mixin_count); }); - watcher->setFuture(future); } PendingTransaction *Wallet::createTransactionAll(const QString &dst_addr, const QString &payment_id, @@ -486,17 +483,10 @@ void Wallet::createTransactionAllAsync(const QString &dst_addr, const QString &p quint32 mixin_count, PendingTransaction::Priority priority) { - QFuture future = QtConcurrent::run(this, &Wallet::createTransactionAll, - dst_addr, payment_id, mixin_count, priority); - QFutureWatcher * watcher = new QFutureWatcher(); - - connect(watcher, &QFutureWatcher::finished, - this, [this, watcher,dst_addr,payment_id,mixin_count]() { - QFuture future = watcher->future(); - watcher->deleteLater(); - emit transactionCreated(future.result(),dst_addr,payment_id,mixin_count); + m_scheduler.run([this, dst_addr, payment_id, mixin_count, priority] { + PendingTransaction *tx = createTransactionAll(dst_addr, payment_id, mixin_count, priority); + emit transactionCreated(tx, dst_addr, payment_id, mixin_count); }); - watcher->setFuture(future); } PendingTransaction *Wallet::createSweepUnmixableTransaction() @@ -508,16 +498,10 @@ PendingTransaction *Wallet::createSweepUnmixableTransaction() void Wallet::createSweepUnmixableTransactionAsync() { - QFuture future = QtConcurrent::run(this, &Wallet::createSweepUnmixableTransaction); - QFutureWatcher * watcher = new QFutureWatcher(); - - connect(watcher, &QFutureWatcher::finished, - this, [this, watcher]() { - QFuture future = watcher->future(); - watcher->deleteLater(); - emit transactionCreated(future.result(),"","",0); + m_scheduler.run([this] { + PendingTransaction *tx = createSweepUnmixableTransaction(); + emit transactionCreated(tx, "", "", 0); }); - watcher->setFuture(future); } UnsignedTransaction * Wallet::loadTxFile(const QString &fileName) @@ -539,18 +523,9 @@ bool Wallet::submitTxFile(const QString &fileName) const void Wallet::commitTransactionAsync(PendingTransaction *t) { - QStringList txid(t->txid()); - QFuture future = QtConcurrent::run(t, &PendingTransaction::commit); - - QFutureWatcher * watcher = new QFutureWatcher(); - - connect(watcher, &QFutureWatcher::finished, - this, [this, watcher, t, txid]() { - QFuture future = watcher->future(); - watcher->deleteLater(); - emit transactionCommitted(future.result(), t, txid); - }); - watcher->setFuture(future); + m_scheduler.run([this, t] { + emit transactionCommitted(t->commit(), t, t->txid()); + }); } void Wallet::disposeTransaction(PendingTransaction *t) @@ -662,23 +637,11 @@ QString Wallet::getTxKey(const QString &txid) const return QString::fromStdString(m_walletImpl->getTxKey(txid.toStdString())); } -void Wallet::getTxKeyAsync(const QString &txid, const QJSValue &ref) +void Wallet::getTxKeyAsync(const QString &txid, const QJSValue &callback) { - QFuture future = QtConcurrent::run(this, &Wallet::getTxKey, txid); - auto watcher = new QFutureWatcher(this); - - connect(watcher, &QFutureWatcher::finished, - this, [watcher, txid, ref]() { - QFuture future = watcher->future(); - watcher->deleteLater(); - - auto txKey = future.result(); - if (ref.isCallable()){ - QJSValue cb(ref); - cb.call(QJSValueList {txid, txKey}); - } - }); - watcher->setFuture(future); + m_scheduler.run([this, txid] { + return QJSValueList({txid, getTxKey(txid)}); + }, callback); } QString Wallet::checkTxKey(const QString &txid, const QString &tx_key, const QString &address) @@ -699,23 +662,11 @@ QString Wallet::getTxProof(const QString &txid, const QString &address, const QS return QString::fromStdString(result); } -void Wallet::getTxProofAsync(const QString &txid, const QString &address, const QString &message, const QJSValue &ref) +void Wallet::getTxProofAsync(const QString &txid, const QString &address, const QString &message, const QJSValue &callback) { - QFuture future = QtConcurrent::run(this, &Wallet::getTxProof, txid, address, message); - auto watcher = new QFutureWatcher(this); - - connect(watcher, &QFutureWatcher::finished, - this, [watcher, txid, ref]() { - QFuture future = watcher->future(); - watcher->deleteLater(); - - auto proof = future.result(); - if (ref.isCallable()){ - QJSValue cb(ref); - cb.call(QJSValueList {txid, proof}); - } - }); - watcher->setFuture(future); + m_scheduler.run([this, txid, address, message] { + return QJSValueList({txid, getTxProof(txid, address, message)}); + }, callback); } QString Wallet::checkTxProof(const QString &txid, const QString &address, const QString &message, const QString &signature) @@ -737,23 +688,11 @@ Q_INVOKABLE QString Wallet::getSpendProof(const QString &txid, const QString &me return QString::fromStdString(result); } -void Wallet::getSpendProofAsync(const QString &txid, const QString &message, const QJSValue &ref) +void Wallet::getSpendProofAsync(const QString &txid, const QString &message, const QJSValue &callback) { - QFuture future = QtConcurrent::run(this, &Wallet::getSpendProof, txid, message); - auto watcher = new QFutureWatcher(this); - - connect(watcher, &QFutureWatcher::finished, - this, [watcher, txid, ref]() { - QFuture future = watcher->future(); - watcher->deleteLater(); - - auto proof = future.result(); - if (ref.isCallable()){ - QJSValue cb(ref); - cb.call(QJSValueList {txid, proof}); - } - }); - watcher->setFuture(future); + m_scheduler.run([this, txid, message] { + return QJSValueList({txid, getSpendProof(txid, message)}); + }, callback); } Q_INVOKABLE QString Wallet::checkSpendProof(const QString &txid, const QString &message, const QString &signature) const @@ -1007,6 +946,7 @@ Wallet::Wallet(Monero::Wallet *w, QObject *parent) , m_daemonBlockChainTargetHeightTtl(DAEMON_BLOCKCHAIN_TARGET_HEIGHT_CACHE_TTL_SECONDS) , m_connectionStatusTtl(WALLET_CONNECTION_STATUS_CACHE_TTL_SECONDS) , m_currentSubaddressAccount(0) + , m_scheduler(this) { m_history = new TransactionHistory(m_walletImpl->history(), this); m_addressBook = new AddressBook(m_walletImpl->addressBook(), this); @@ -1028,6 +968,9 @@ Wallet::Wallet(Monero::Wallet *w, QObject *parent) Wallet::~Wallet() { qDebug("~Wallet: Closing wallet"); + + m_scheduler.shutdownWaitForFinished(); + delete m_addressBook; m_addressBook = NULL; diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index f3726e91..95d4a954 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -37,6 +37,7 @@ #include #include "wallet/api/wallet2_api.h" // we need to have an access to the Monero::Wallet::Status enum here; +#include "qt/FutureScheduler.h" #include "PendingTransaction.h" // we need to have an access to the PendingTransaction::Priority enum here; #include "UnsignedTransaction.h" #include "NetworkType.h" @@ -182,7 +183,7 @@ public: //! returns if view only wallet Q_INVOKABLE bool viewOnly() const; - Q_INVOKABLE void refreshHeightAsync() const; + Q_INVOKABLE void refreshHeightAsync(); //! export/import key images Q_INVOKABLE bool exportKeyImages(const QString& path); @@ -290,13 +291,13 @@ public: Q_INVOKABLE bool setUserNote(const QString &txid, const QString ¬e); Q_INVOKABLE QString getUserNote(const QString &txid) const; Q_INVOKABLE QString getTxKey(const QString &txid) const; - Q_INVOKABLE void getTxKeyAsync(const QString &txid, const QJSValue &ref); + Q_INVOKABLE void getTxKeyAsync(const QString &txid, const QJSValue &callback); Q_INVOKABLE QString checkTxKey(const QString &txid, const QString &tx_key, const QString &address); Q_INVOKABLE QString getTxProof(const QString &txid, const QString &address, const QString &message) const; - Q_INVOKABLE void getTxProofAsync(const QString &txid, const QString &address, const QString &message, const QJSValue &ref); + Q_INVOKABLE void getTxProofAsync(const QString &txid, const QString &address, const QString &message, const QJSValue &callback); Q_INVOKABLE QString checkTxProof(const QString &txid, const QString &address, const QString &message, const QString &signature); Q_INVOKABLE QString getSpendProof(const QString &txid, const QString &message) const; - Q_INVOKABLE void getSpendProofAsync(const QString &txid, const QString &message, const QJSValue &ref); + Q_INVOKABLE void getSpendProofAsync(const QString &txid, const QString &message, const QJSValue &callback); Q_INVOKABLE QString checkSpendProof(const QString &txid, const QString &message, const QString &signature) const; // Rescan spent outputs Q_INVOKABLE bool rescanSpent(); @@ -356,7 +357,7 @@ signals: // emitted when transaction is created async void transactionCreated(PendingTransaction * transaction, QString address, QString paymentId, quint32 mixinCount); - void connectionStatusChanged(ConnectionStatus status) const; + void connectionStatusChanged(int status) const; private: Wallet(QObject * parent = nullptr); @@ -406,6 +407,7 @@ private: QString m_daemonUsername; QString m_daemonPassword; Monero::WalletListener *m_walletListener; + FutureScheduler m_scheduler; }; diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index 790ad561..22a810b9 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -145,17 +145,9 @@ Wallet *WalletManager::openWallet(const QString &path, const QString &password, void WalletManager::openWalletAsync(const QString &path, const QString &password, NetworkType::Type nettype, quint64 kdfRounds) { - QFuture future = QtConcurrent::run(this, &WalletManager::openWallet, - path, password, nettype, kdfRounds); - QFutureWatcher * watcher = new QFutureWatcher(); - - connect(watcher, &QFutureWatcher::finished, - this, [this, watcher]() { - QFuture future = watcher->future(); - watcher->deleteLater(); - emit walletOpened(future.result()); + m_scheduler.run([this, path, password, nettype, kdfRounds] { + emit walletOpened(openWallet(path, password, nettype, kdfRounds)); }); - watcher->setFuture(future); } @@ -216,21 +208,10 @@ Wallet *WalletManager::createWalletFromDevice(const QString &path, const QString void WalletManager::createWalletFromDeviceAsync(const QString &path, const QString &password, NetworkType::Type nettype, const QString &deviceName, quint64 restoreHeight, const QString &subaddressLookahead) { - auto lmbd = [=](){ - return this->createWalletFromDevice(path, password, nettype, deviceName, restoreHeight, subaddressLookahead); - }; - - QFuture future = QtConcurrent::run(lmbd); - - QFutureWatcher * watcher = new QFutureWatcher(); - - connect(watcher, &QFutureWatcher::finished, - this, [this, watcher]() { - QFuture future = watcher->future(); - watcher->deleteLater(); - emit walletCreated(future.result()); - }); - watcher->setFuture(future); + m_scheduler.run([this, path, password, nettype, deviceName, restoreHeight, subaddressLookahead] { + Wallet *wallet = createWalletFromDevice(path, password, nettype, deviceName, restoreHeight, subaddressLookahead); + emit walletCreated(wallet); + }); } QString WalletManager::closeWallet() @@ -249,16 +230,9 @@ QString WalletManager::closeWallet() void WalletManager::closeWalletAsync() { - QFuture future = QtConcurrent::run(this, &WalletManager::closeWallet); - QFutureWatcher * watcher = new QFutureWatcher(); - - connect(watcher, &QFutureWatcher::finished, - this, [this, watcher]() { - QFuture future = watcher->future(); - watcher->deleteLater(); - emit walletClosed(future.result()); + m_scheduler.run([this] { + emit walletClosed(closeWallet()); }); - watcher->setFuture(future); } bool WalletManager::walletExists(const QString &path) const @@ -333,7 +307,7 @@ QString WalletManager::paymentIdFromAddress(const QString &address, NetworkType: void WalletManager::setDaemonAddressAsync(const QString &address) { - QtConcurrent::run([this, address] { + m_scheduler.run([this, address] { m_pimpl->setDaemonAddress(address.toStdString()); }); } @@ -376,9 +350,9 @@ bool WalletManager::isMining() const return m_pimpl->isMining(); } -void WalletManager::miningStatusAsync() const +void WalletManager::miningStatusAsync() { - QtConcurrent::run([this] { + m_scheduler.run([this] { emit miningStatus(isMining()); }); } @@ -488,19 +462,11 @@ bool WalletManager::saveQrCode(const QString &code, const QString &path) const return QRCodeImageProvider::genQrImage(code, &size).scaled(size.expandedTo(QSize(240, 240)), Qt::KeepAspectRatio).save(path, "PNG", 100); } -void WalletManager::checkUpdatesAsync(const QString &software, const QString &subdir) const +void WalletManager::checkUpdatesAsync(const QString &software, const QString &subdir) { - QFuture future = QtConcurrent::run(this, &WalletManager::checkUpdates, - software, subdir); - QFutureWatcher * watcher = new QFutureWatcher(); - connect(watcher, &QFutureWatcher::finished, - this, [this, watcher]() { - QFuture future = watcher->future(); - watcher->deleteLater(); - qDebug() << "Checking for updates - done"; - emit checkUpdatesComplete(future.result()); + m_scheduler.run([this, software, subdir] { + emit checkUpdatesComplete(checkUpdates(software, subdir)); }); - watcher->setFuture(future); } @@ -532,11 +498,18 @@ bool WalletManager::clearWalletCache(const QString &wallet_path) const return walletCache.rename(newFileName); } -WalletManager::WalletManager(QObject *parent) : QObject(parent) +WalletManager::WalletManager(QObject *parent) + : QObject(parent) + , m_scheduler(this) { m_pimpl = Monero::WalletManagerFactory::getWalletManager(); } +WalletManager::~WalletManager() +{ + m_scheduler.shutdownWaitForFinished(); +} + void WalletManager::onWalletPassphraseNeeded(Monero::Wallet *) { m_mutex_pass.lock(); diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h index 3acfa845..18256b1c 100644 --- a/src/libwalletqt/WalletManager.h +++ b/src/libwalletqt/WalletManager.h @@ -37,6 +37,7 @@ #include #include #include +#include "qt/FutureScheduler.h" #include "NetworkType.h" class Wallet; @@ -151,7 +152,7 @@ public: Q_INVOKABLE bool localDaemonSynced() const; Q_INVOKABLE bool isDaemonLocal(const QString &daemon_address) const; - Q_INVOKABLE void miningStatusAsync() const; + Q_INVOKABLE void miningStatusAsync(); Q_INVOKABLE bool startMining(const QString &address, quint32 threads, bool backgroundMining, bool ignoreBattery); Q_INVOKABLE bool stopMining(); @@ -175,7 +176,7 @@ public: Q_INVOKABLE bool parse_uri(const QString &uri, QString &address, QString &payment_id, uint64_t &amount, QString &tx_description, QString &recipient_name, QVector &unknown_parameters, QString &error) const; Q_INVOKABLE QVariantMap parse_uri_to_object(const QString &uri) const; Q_INVOKABLE bool saveQrCode(const QString &, const QString &) const; - Q_INVOKABLE void checkUpdatesAsync(const QString &software, const QString &subdir) const; + Q_INVOKABLE void checkUpdatesAsync(const QString &software, const QString &subdir); Q_INVOKABLE QString checkUpdates(const QString &software, const QString &subdir) const; // clear/rename wallet cache @@ -200,6 +201,7 @@ private: friend class WalletPassphraseListenerImpl; explicit WalletManager(QObject *parent = 0); + ~WalletManager(); bool isMining() const; @@ -212,6 +214,8 @@ private: QMutex m_mutex_pass; QString m_passphrase; bool m_passphrase_abort; + + FutureScheduler m_scheduler; }; #endif // WALLETMANAGER_H diff --git a/src/qt/FutureScheduler.cpp b/src/qt/FutureScheduler.cpp new file mode 100644 index 00000000..bdd47829 --- /dev/null +++ b/src/qt/FutureScheduler.cpp @@ -0,0 +1,89 @@ +#include "FutureScheduler.h" + +FutureScheduler::FutureScheduler(QObject *parent) + : QObject(parent), Alive(0), Stopping(false) +{ +} + +FutureScheduler::~FutureScheduler() +{ + shutdownWaitForFinished(); +} + +void FutureScheduler::shutdownWaitForFinished() noexcept +{ + QMutexLocker locker(&Mutex); + + Stopping = true; + while (Alive > 0) + { + Condition.wait(&Mutex); + } +} + +QPair> FutureScheduler::run(std::function function) noexcept +{ + return execute([this, function](QFutureWatcher *) { + return QtConcurrent::run([this, function] { + try + { + function(); + } + catch (const std::exception &exception) + { + qWarning() << "Exception thrown from async function: " << exception.what(); + } + done(); + }); + }); +} + +QPair> FutureScheduler::run(std::function function, const QJSValue &callback) noexcept +{ + if (!callback.isCallable()) + { + throw std::runtime_error("js callback must be callable"); + } + + return execute([this, function, callback](QFutureWatcher *watcher) { + connect(watcher, &QFutureWatcher::finished, [watcher, callback] { + QJSValue(callback).call(watcher->future().result()); + }); + return QtConcurrent::run([this, function] { + QJSValueList result; + try + { + result = function(); + } + catch (const std::exception &exception) + { + qWarning() << "Exception thrown from async function: " << exception.what(); + } + done(); + return result; + }); + }); +} + +bool FutureScheduler::add() noexcept +{ + QMutexLocker locker(&Mutex); + + if (Stopping) + { + return false; + } + + ++Alive; + return true; +} + +void FutureScheduler::done() noexcept +{ + { + QMutexLocker locker(&Mutex); + --Alive; + } + + Condition.wakeAll(); +} diff --git a/src/qt/FutureScheduler.h b/src/qt/FutureScheduler.h new file mode 100644 index 00000000..28c2dfdf --- /dev/null +++ b/src/qt/FutureScheduler.h @@ -0,0 +1,79 @@ +#ifndef FUTURE_SCHEDULER_H +#define FUTURE_SCHEDULER_H + +#include + +#include +#include +#include +#include +#include +#include +#include + +class FutureScheduler : public QObject +{ + Q_OBJECT + +public: + FutureScheduler(QObject *parent); + ~FutureScheduler(); + + void shutdownWaitForFinished() noexcept; + + QPair> run(std::function function) noexcept; + QPair> run(std::function function, const QJSValue &callback) noexcept; + +private: + bool add() noexcept; + void done() noexcept; + + template + QFutureWatcher *newWatcher() + { + QFutureWatcher *watcher = new QFutureWatcher(); + QThread *schedulerThread = this->thread(); + if (watcher->thread() != schedulerThread) + { + watcher->moveToThread(schedulerThread); + } + watcher->setParent(this); + + return watcher; + } + + template + QPair> execute(std::function(QFutureWatcher *)> makeFuture) noexcept + { + if (add()) + { + try + { + auto *watcher = newWatcher(); + watcher->setFuture(makeFuture(watcher)); + connect(watcher, &QFutureWatcher::finished, [this, watcher] { + watcher->deleteLater(); + }); + return qMakePair(true, watcher->future()); + } + catch (const std::exception &exception) + { + qCritical() << "Failed to schedule async function: " << exception.what(); + done(); + } + } + + return qMakePair(false, QFuture()); + } + + QFutureWatcher schedule(std::function function); + QFutureWatcher schedule(std::function function, const QJSValue &callback); + +private: + size_t Alive; + QWaitCondition Condition; + QMutex Mutex; + bool Stopping; +}; + +#endif // FUTURE_SCHEDULER_H