Skip to content

Conversation

@hebasto
Copy link
Member

@hebasto hebasto commented May 21, 2021

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:

DeepinScreenshot_select-area_20210522230626

Fixes #247.

@hebasto hebasto requested review from promag and ryanofsky May 21, 2021 19:51
@hebasto hebasto added the Wallet label May 22, 2021
@hebasto hebasto marked this pull request as draft May 22, 2021 19:37
@hebasto hebasto closed this May 22, 2021
@hebasto hebasto reopened this May 22, 2021
@hebasto hebasto added the UX All about "how to get things done" label May 22, 2021
@hebasto hebasto marked this pull request as ready for review May 22, 2021 20:16
Copy link
Contributor

@promag promag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK.

// Workaround for macOS-only Qt bug; see: QTBUG-65750, QTBUG-70357.
const int margin = TextWidth(dialog->fontMetrics(), ("X"));
dialog->resize(dialog->width() + 2 * margin, dialog->height());
dialog->show();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7585010

Direct call show() breaks that behavior on macos.

By "break" you mean it always shows, doesn't take into account minimumDuration. Only macos?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

m_progress_dialog->setCancelButton(nullptr);
m_progress_dialog->setWindowModality(Qt::ApplicationModal);
GUIUtil::PolishProgressDialog(m_progress_dialog);
auto progress_dialog = new QProgressDialog(m_parent_widget);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dc12410

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "qt, wallet: Move activity progress dialog from data member to local" (6035536)

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.

, m_wallet_controller(wallet_controller)
, m_parent_widget(parent_widget)
{
connect(this, &WalletControllerActivity::finished, this, &QObject::deleteLater);
Copy link
Contributor

@promag promag May 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4517c51

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.

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Light code review ACK 8c9a9a3

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:

RunAsynchronously([this] {
   for (std::unique_ptr<interfaces::Wallet>& wallet : node().walletClient().getWallets()) {
       m_wallet_controller->getOrCreateWallet(std::move(wallet));
   }
})

I did implement something like this previously (but with more features) as an experiment in bitcoin/bitcoin@master...ryanofsky:pr/gsync comment bitcoin/bitcoin#16874 (comment)

@promag
Copy link
Contributor

promag commented May 31, 2021

@ryanofsky agree, that or 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.

Copy link
Contributor

@promag promag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hebasto what do you think about splitting this PR?

@hebasto
Copy link
Member Author

hebasto commented Sep 5, 2021

@promag

@hebasto what do you think about splitting this PR?

Sounds good. In what parts?

Copy link
Contributor

@shaavan shaavan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tACK 8c9a9a3
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.
Screenshot from 2021-09-06 20-06-46

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:

  • qt, macos: Fix GUIUtil::PolishProgressDialog bug
    • Done to prevent misbehaving of this PR in macOS.
  • qt: Improve GUI responsiveness
    • Removed 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.
    • Under WalletControllerActivity::showProgressDialog function, setValue is used for the m_progress_dialog variable. This starts the internal duration estimation.
    • The now redundant setMinimumDuration instruction from BitcoinGUI::showProgress and WalletView::showProgress functions.
  • qt, wallet: Move activity progress dialog from data member to local
    • Since the activity progress dialog has been moved from data member to local variable, subsequent changes have been made:
      • WalletControllerActivity destructor has been restored to default destructor.
      • In WalletControllerActivity::showProgressDialog function:
        • m_progress_dialog variable has been renamed to progress_dialog to avoid naming confusion with a data member.
        • The non-existence of m_progress_dialog (or progress_dialog) is not being asserted now.
      • The 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.
  • qt, wallet, refactor: Move connection to QObject::deleteLater to ctor
    • Instead of creating similar connections multiple times for individual objects of class, the connection has been moved to the constructor of the common parent class itself, resulting in simplification of the code.
  • qt, wallet: Add LoadWalletsActivity
    • New LoadWalletsActivity class is declared as a child class of the WalletControllerActivity class.
    • A function Load is added to the class that calls the showProgressDialog function and loads the wallets.
    • This new class and its functions are appropriately declared under the walletcontroler.h file
  • qt, wallet: Move wallets loading out from the main GUI thread
    • The new LoadWalletActivity::Load function is now used to load wallets at the start of the GUI.
    • The redundant code, which is now part of the Load function, has been removed from the WalletController constructor.

I hope these notes will be of some help to fellow reviewers.

@hebasto
Copy link
Member Author

hebasto commented Sep 7, 2021

Rebased 8c9a9a3 -> e075098 (pr342.02 -> pr342.03).

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review ACK e075098. I still think the need for these activity classes and timer hacks is unfortunate, because it should be possible to have a utility function that takes a lambda, runs it asynchronously and posts a result back to the GUI thread. (The lambda would also be able to capture variables so would not be precluded from receiving input or posting incremental feedback either.) But the activity class here is pretty small and this suggestion isn't very important, just something to keep in mind if we are looking to reduce boilerplate and improve readability later.

m_progress_dialog->setCancelButton(nullptr);
m_progress_dialog->setWindowModality(Qt::ApplicationModal);
GUIUtil::PolishProgressDialog(m_progress_dialog);
auto progress_dialog = new QProgressDialog(m_parent_widget);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "qt, wallet: Move activity progress dialog from data member to local" (6035536)

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.

GUIUtil::ExceptionSafeConnect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet);
connect(wallet_controller, &WalletController::walletRemoved, this, &BitcoinGUI::removeWallet);

for (WalletModel* wallet_model : m_wallet_controller->getOpenWallets()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "qt, wallet: Move wallets loading out from the main GUI thread" (e075098)

The getOpenWallets wallets method seems unused after this change and could be dropped

Comment on lines 356 to 348
for (std::unique_ptr<interfaces::Wallet>& wallet : node().walletClient().getWallets()) {
m_wallet_controller->getOrCreateWallet(std::move(wallet));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "qt, wallet: Add LoadWalletsActivity" (6edff4d)

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.

@ryanofsky
Copy link
Contributor

It seems like this would fix #247 by making the gui responsive during walletmodel loading. Is this the case? It might also be good to mention in the PR description

@hebasto
Copy link
Member Author

hebasto commented Sep 9, 2021

@ryanofsky @shaavan @promag

Thank you for your reviews!

Updated e075098 -> 2fe69ef (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

  • rebased on top of the recent changes in CI

@ryanofsky

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 -- bitcoin/bitcoin#22231 and this one.

UPDATE: #342 (comment)

It might also be good to mention in the PR description

Done.

@promag
Copy link
Contributor

promag commented Sep 11, 2021

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.

@hebasto
Copy link
Member Author

hebasto commented Sep 12, 2021

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.

Yeah, TransactionTablePriv::refreshWallet could really take much time for big wallets.

@hebasto
Copy link
Member Author

hebasto commented Sep 12, 2021

@ryanofsky

It seems like this would fix #247 by making the gui responsive during walletmodel loading. Is this the case? It might also be good to mention in the PR description

I've re-read logs provided by @laanwj in #247 (comment) and I see now you are right.

{
showProgressDialog(tr("Loading wallets…"));

QTimer::singleShot(0, worker(), [this] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use GUIUtil::ObjectInvoke here and below?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather keep this PR as is, and in a follow up use GUIUtil::ObjectInvoke in a wider scope of all walletcontroller.cpp.


QTimer::singleShot(0, worker(), [this] {
for (auto& wallet : node().walletClient().getWallets()) {
m_wallet_controller->getOrCreateWallet(std::move(wallet));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, we could show progress and/or show each wallet name.

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review ACK 2fe69ef. Just suggested changes since last review: squashing commits and dropping unused method (thanks!)

Copy link
Contributor

@shaavan shaavan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reACK 2fe69ef

Updates since my last review:

  • PR rebased on current master
  • Two Commits. qt, macOS: Fix GUIUtil::PolishProgressDialog bug and qt: Improve GUI responsiveness are dropped because they are no longer needed.
  • Made appropriate changes to other commits and removed parts that are no longer needed or used.
  • qt, wallet: Move wallets loading out from the main GUI thread and qt, wallet: Add LoadWalletsActivity class have been squashed together.
  • New commit qt, wallet: Drop no longer used WalletController::getOpenWallets() which, as it say so, removed getOpenWallets() function.

Copy link
Contributor

@promag promag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review ACK 2fe69ef.

@hebasto hebasto merged commit 9013f23 into bitcoin-core:master Sep 30, 2021
@hebasto hebasto deleted the 210521-wallet branch September 30, 2021 14:37
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Sep 30, 2021
@bitcoin-core bitcoin-core locked as resolved and limited conversation to collaborators Sep 30, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

UX All about "how to get things done" Wallet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bitcoin-qt stays at "Done loading" for a long time

6 participants