This PR improves the GUI responsiveness during initial wallets loading at startup (especially ones that have tons of txs), and shows a standard progress dialog for long loading:
Fixes #247.
Some occurrences of a seg fault in tests:
817@@ -818,7 +818,6 @@ void PolishProgressDialog(QProgressDialog* dialog)
818 // Workaround for macOS-only Qt bug; see: QTBUG-65750, QTBUG-70357.
819 const int margin = TextWidth(dialog->fontMetrics(), ("X"));
820 dialog->resize(dialog->width() + 2 * margin, dialog->height());
821- dialog->show();
75850106aeecfed1d2dc16d8a67ec210c5826a47
Direct call show() breaks that behavior on macos.
By “break” you mean it always shows, doesn’t take into account minimumDuration
. Only macos?
By “break” you mean it always shows, doesn’t take into account
minimumDuration
.
Exactly.
Only macos?
The removed code is placed within #ifdef Q_OS_MAC
.
205- m_progress_dialog->setLabelText(label_text);
206- m_progress_dialog->setRange(0, 0);
207- m_progress_dialog->setCancelButton(nullptr);
208- m_progress_dialog->setWindowModality(Qt::ApplicationModal);
209- GUIUtil::PolishProgressDialog(m_progress_dialog);
210+ auto progress_dialog = new QProgressDialog(m_parent_widget);
dc124101cc50ca8dfd26796f26caf87316f3182d
This approach is fine for indeterminate progress dialog. If we want to change the progress or update the label text then having the member m_progress_dialog
is better.
Either way, I think this change is unrelated to the goal of this PR.
In commit “qt, wallet: Move activity progress dialog from data member to local” (6035536d8875f9cf3561048abd486272756e15dc)
Either way, I think this change is unrelated to the goal of this PR.
People will have different ideas of what’s related/unrelated, but I think this commit fits in with the rest of the PR by making sure activities are constructed/destroyed/used the same way. I can see how keeping m_progress_dialog
pointer might make it possible to attach more inputs to the dialog later, but this apparently isn’t needed right now, and even trying to do it seems like it would involve other complications like managing the pointer lifetime. By contrast the approach here of just connecting signals right away and not keeping the pointer seems simple while still being extensible.
189@@ -190,6 +190,7 @@ WalletControllerActivity::WalletControllerActivity(WalletController* wallet_cont
190 , m_wallet_controller(wallet_controller)
191 , m_parent_widget(parent_widget)
192 {
193+ connect(this, &WalletControllerActivity::finished, this, &QObject::deleteLater);
4517c51b8605fbf91e2319b16d7d5dcf80cb1565
Originally the idea was to allow the user to see activity details and then the user could dismiss or it could auto dismiss. I also think this is an unrelated change - this PR could also connect LoadWalletsActivity
finished signal. But this is also fine and can be improved if later the original idea is implemented.
Light code review ACK 8c9a9a39876afc650ebb3a54becd652e9b03e66f
I do think it is unfortunate so much boilerplate (WalletControllerActivity class definition, multiple methods, multiple single shot timers and signals) is added just to run 3 lines of code asynchronously.
It should be possible to have reusable utility:
0RunAsynchronously([this] {
1 for (std::unique_ptr<interfaces::Wallet>& wallet : node().walletClient().getWallets()) {
2 m_wallet_controller->getOrCreateWallet(std::move(wallet));
3 }
4})
I did implement something like this previously (but with more features) as an experiment in https://github.com/bitcoin/bitcoin/compare/master...ryanofsky:pr/gsync comment https://github.com/bitcoin/bitcoin/issues/16874#issuecomment-632424392
QtConcurrent::run
or QMetaObject::invokeMethod
(requires min Qt 5.10 IIRC) to just run some code async. But if you want to show progress feedback, update something with “loading wallet x”, allow the user to interrupt, maybe pause, at the end ask something else, then wrapping the activity state in some class makes sense to me.
tACK 8c9a9a39876afc650ebb3a54becd652e9b03e66f Tested on Ubuntu 20.04 (Using Qt version 5.12.8)
This PR adds a ‘Loading Wallet’ progress window for the initial loading of opened wallet after opening GUI. This PR also moves wallet loading out of the main GUI thread while simplifying some other aspects of the codebase.
I was able to test the PR successfully. I have added a screenshot for the resultant progress window I observed while opening the GUI.
The changes made in this PR are pretty extensive and detailed. Thanks for this fantastic work, @hebasto. It took me quite a while to understand everything that has been done in this PR. That’s why I am sharing my notes that discuss the changes in quite detail. So that other reviewers could have an easier time reviewing this PR. If some of my observations are erroneous, please do inform me.
NOTES:
Q_UNUSED
because now dialog is being used at least once in any case under this function.setMinimumDuration
is set through the PolishProgressDialog
function. Since this function is used multiple times when setting setMinimumDuration
was required, it makes sense to move that in this function. This change simplifies the code while maintaining the same functionality.WalletControllerActivity::showProgressDialog
function, setValue
is used for the m_progress_dialog
variable. This starts the internal duration estimation.setMinimumDuration
instruction from BitcoinGUI::showProgress
and WalletView::showProgress
functions.WalletControllerActivity
destructor has been restored to default destructor.WalletControllerActivity::showProgressDialog
function:
m_progress_dialog
variable has been renamed to progress_dialog
to avoid naming confusion with a data member.m_progress_dialog
(or progress_dialog) is not being asserted now.destroyProgressDialog
function is also removed. Because now progress_dialog doesn’t exist out of the function scope and hence no need for a progress_dialog
destructor.LoadWalletsActivity
class is declared as a child class of the WalletControllerActivity
class.Load
is added to the class that calls the showProgressDialog
function and loads the wallets.walletcontroler.h
fileLoadWalletActivity::Load
function is now used to load wallets at the start of the GUI.I hope these notes will be of some help to fellow reviewers.
658@@ -659,9 +659,8 @@ void BitcoinGUI::setWalletController(WalletController* wallet_controller)
659 GUIUtil::ExceptionSafeConnect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet);
660 connect(wallet_controller, &WalletController::walletRemoved, this, &BitcoinGUI::removeWallet);
661
662- for (WalletModel* wallet_model : m_wallet_controller->getOpenWallets()) {
In commit “qt, wallet: Move wallets loading out from the main GUI thread” (e07509867ec14ccb4f2f5bce082f73b77ade7743)
The getOpenWallets
wallets method seems unused after this change and could be dropped
353+ showProgressDialog(tr("Loading wallets…"));
354+
355+ QTimer::singleShot(0, worker(), [this] {
356+ for (std::unique_ptr<interfaces::Wallet>& wallet : node().walletClient().getWallets()) {
357+ m_wallet_controller->getOrCreateWallet(std::move(wallet));
358+ }
In commit “qt, wallet: Add LoadWalletsActivity” (6edff4dfa508100564c3152bbccc7b37ac777528)
Note: This is not new code, this is existing code which is just being moved. The original code is deleted in the next commit. I would squash two commits for more clarity here, but it’s not important, and of course the best way to split and group commits is subjective.
Also this commit moves wallets loading out from the main GUI thread.
Thank you for your reviews!
Updated e07509867ec14ccb4f2f5bce082f73b77ade7743 -> 2fe69efbc607fdcc3657637d59a38cc5b4db2d05 (pr342.03 -> pr342.04):
The
getOpenWallets
wallets method seems unused after this change and could be dropped
This is not new code, this is existing code which is just being moved. The original code is deleted in the next commit. I would squash two commits for more clarity here
It seems like this would fix #247 by making the gui responsive during walletmodel loading. Is this the case?
~I don’t think creating WalletModel
instances is the most time consuming process. Another part is WalletClient::start()
. And I believe to fix #247, both PRs are required – https://github.com/bitcoin/bitcoin/pull/22231 and this one.~
UPDATE: #342 (comment)
It might also be good to mention in the PR description
Done.
I don’t think creating
WalletModel
instances is the most time consuming process.
This is not true, in fact, it’s the most expensive task, because on WalletModel
constructor AddressTableModel
and TransactionTableModel
are also instanced, and these prefetch wallet addresses and transactions.
I don’t think creating
WalletModel
instances is the most time consuming process.This is not true, in fact, it’s the most expensive task, because on
WalletModel
constructorAddressTableModel
andTransactionTableModel
are also instanced, and these prefetch wallet addresses and transactions.
Yeah, TransactionTablePriv::refreshWallet
could really take much time for big wallets.
340+
341+void LoadWalletsActivity::load()
342+{
343+ showProgressDialog(tr("Loading wallets…"));
344+
345+ QTimer::singleShot(0, worker(), [this] {
GUIUtil::ObjectInvoke
here and below?
GUIUtil::ObjectInvoke
in a wider scope of all walletcontroller.cpp
.
342+{
343+ showProgressDialog(tr("Loading wallets…"));
344+
345+ QTimer::singleShot(0, worker(), [this] {
346+ for (auto& wallet : node().walletClient().getWallets()) {
347+ m_wallet_controller->getOrCreateWallet(std::move(wallet));
reACK 2fe69efbc607fdcc3657637d59a38cc5b4db2d05
Updates since my last review:
getOpenWallets()
function.