Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(qt): refresh the whole wallet instead of processing individual updates for huge notification queues #5453

Merged
merged 2 commits into from
Jun 28, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 22 additions & 9 deletions src/qt/transactiontablemodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class TransactionTablePriv
void refreshWallet(interfaces::Wallet& wallet)
{
qDebug() << "TransactionTablePriv::refreshWallet";
parent->beginResetModel();
cachedWallet.clear();
{
for (const auto& wtx : wallet.getWalletTxs()) {
Expand All @@ -82,6 +83,7 @@ class TransactionTablePriv
}
}
}
parent->endResetModel();
Copy link
Collaborator

Choose a reason for hiding this comment

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

would it be a valid state of parent if an exception would be thrown before endResetModel() is called?

Copy link
Author

Choose a reason for hiding this comment

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

Hmm.. Not sure I get the question.. What kind of exception? And why you think it should affect parent (TransactionTableModel*)?

Copy link
Collaborator

Choose a reason for hiding this comment

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

https://doc.qt.io/qt-6/qabstractitemmodel.html#endResetModel

You must call this function after resetting any internal data structure in your model or proxy model.

I see that some methods doesn't have noexcept specifier, more over functions between beginResetModel() and endResetModel() such as getWalletTxs, showTransaction have allocation inside: it means that any time an exception can be thrown.

There're two possible solution for resolve "you must call" request:

  • write a wrapper such as lock_guard that will call endResetModel() in a destructor.
  • wrap everything between begin & endResetModel to try-catch and safely call endResetModel

}

/* Update our model of the wallet incrementally, to synchronize our model of the wallet
Expand Down Expand Up @@ -244,6 +246,11 @@ TransactionTableModel::~TransactionTableModel()
delete priv;
}

void TransactionTableModel::refreshWallet()
{
priv->refreshWallet(walletModel->wallet());
}

/** Updates the column title to "Amount (DisplayUnit)" and emits headerDataChanged() signal for table headers to react. */
void TransactionTableModel::updateAmountColumnTitle()
{
Expand Down Expand Up @@ -803,18 +810,24 @@ static void ShowProgress(TransactionTableModel *ttm, const std::string &title, i
if (nProgress == 100)
{
fQueueNotifications = false;
if (vQueueNotifications.size() > 10) { // prevent balloon spam, show maximum 10 balloons
bool invoked = QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, true));
assert(invoked);
}
for (unsigned int i = 0; i < vQueueNotifications.size(); ++i)
{
if (vQueueNotifications.size() - i <= 10) {
bool invoked = QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, false));
if (vQueueNotifications.size() < 10000) {
Copy link
Member

Choose a reason for hiding this comment

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

can you justify this magic number a bit more? why not 500 or 1k or 100k?

Copy link
Author

Choose a reason for hiding this comment

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

Processing 10K notifications worked ok-ish, with a slight delay for my 500k+ txes wallet. Processing 50-100k was already too slow comparing to refreshing the wallet instead. So I just kept 10k as a threshold. Could be higher or could be lower on different machines I guess, not sure how to pick the right number here.

if (vQueueNotifications.size() > 10) { // prevent balloon spam, show maximum 10 balloons
bool invoked = QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, true));
assert(invoked);
}
for (unsigned int i = 0; i < vQueueNotifications.size(); ++i)
{
if (vQueueNotifications.size() - i <= 10) {
bool invoked = QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, false));
assert(invoked);
}

vQueueNotifications[i].invoke(ttm);
vQueueNotifications[i].invoke(ttm);
}
} else {
// it's much faster to just refresh the whole thing instead
bool invoked = QMetaObject::invokeMethod(ttm, "refreshWallet", Qt::QueuedConnection);
assert(invoked);
}
std::vector<TransactionNotification >().swap(vQueueNotifications); // clear
}
Expand Down
2 changes: 2 additions & 0 deletions src/qt/transactiontablemodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ class TransactionTableModel : public QAbstractTableModel
QVariant txAddressDecoration(const TransactionRecord *wtx) const;

public Q_SLOTS:
/* Refresh the whole wallet, helpful for huge notification queues */
void refreshWallet();
/* New transaction, or transaction changed status */
void updateTransaction(const QString &hash, int status, bool showTransaction);
void updateAddressBook(const QString &address, const QString &label,
Expand Down