Skip to content

Commit 138e4fa

Browse files
hebastoUdjinM6
authored andcommitted
Merge bitcoin-core/gui#693: Fix segfault when shutdown during wallet open
9a1d73f Fix segfault when shutdown during wallet open (John Moffett) Pull request description: Fixes #689 ## Summary If you open a wallet and send a shutdown signal during that process, you'll get a segfault when the wallet finishes opening. That's because the `WalletController` object gets deleted manually in bitcoin.cpp during shutdown, but copies of the pointer (and pointers to child objects) are dangling in various places and are accessed in queued events after the deletion. ## Details The issue in #689 is caused by the following sequence of events: 1. Wallet open modal dialog is shown and worker thread does the actual work. 2. Every 200ms, the main event loop checks to see if a shutdown has been requested, but only if a modal is not being shown. 3. Request a shutdown while the modal window is shown. 4. The wallet open process completes, the modal window is dismissed, and various `finish` signals are sent. 5. During handling of one of the `finish` signals, `qApp->processEvents()` is [called](https://github.com/bitcoin-core/gui/blob/e9262ea32a6e1d364fb7974844fadc36f931f8c6/src/qt/sendcoinsdialog.cpp#L603), which causes the main event loop to detect the shutdown (now that the modal window has been dismissed). The `WalletController` and all the `WalletModel`s are [deleted](https://github.com/bitcoin-core/gui/blob/65de8eeeca29e71378aa34602b287ab921b040e4/src/qt/bitcoin.cpp#L394-L401). 6. Control returns to the `finish` method, which eventually tries to send a [signal](https://github.com/bitcoin-core/gui/blob/e9262ea32a6e1d364fb7974844fadc36f931f8c6/src/qt/sendcoinsdialog.cpp#L167) from a wallet model, but it's been deleted already (and the signal is sent from a now-[dangling](https://github.com/bitcoin-core/gui/blob/d8bdee0fc889def7c5f5076da13db4fce0a3728a/src/qt/walletview.cpp#L65) pointer). The simplest fix for that is to change the `qApp->processEvents()` into a `QueuedConnection` call. (The `qApp->processEvents() was a [workaround](bitcoin#593 (comment)) to get the GUI to scroll to the last item in a list that just got added, and this is just a safer way of doing that). However, once that segfault is fixed, another segfault occurs due to some queued wallet events happening after the wallet controller object is deleted here: https://github.com/bitcoin-core/gui/blob/65de8eeeca29e71378aa34602b287ab921b040e4/src/qt/bitcoin.cpp#L394-L401 Since `m_wallet_controller` is a copy of that pointer in `bitcoingui.cpp`, it's now dangling and `if(null)` checks won't work correctly. For instance, this line: https://github.com/bitcoin-core/gui/blob/65de8eeeca29e71378aa34602b287ab921b040e4/src/qt/bitcoingui.cpp#L413 sets up a `QueuedConnection` to `setCurrentWallet`, but by the time control reaches that method (one event cycle after shutdown deleted `m_wallet_controller` in `bitcoin.cpp`), the underlying objects have been destroyed (but the pointers are still dangling). Ideally, we'd use a `QPointer` or `std::shared_ptr / std::weak_ptr`s for these, but the changes would be more involved. This is a minimal fix for the issues. Just set `m_wallet_controller` to `nullptr` in `bitcoingui.cpp`, check its value in a couple places, and avoid a use of `qApp->processEvents`. ACKs for top commit: hebasto: ACK 9a1d73f, I have reviewed the code and it looks OK. furszy: ACK bitcoin-core/gui@9a1d73f Tree-SHA512: a1b94676eb2fcb7606e68fab443b1565b4122aab93c35382b561842a049f4b43fecc459535370d67a64d6ebc4bcec0ebcda981fff633ebd41bdba6f7093ea540
1 parent 7fc9a7b commit 138e4fa

File tree

3 files changed

+17
-6
lines changed

3 files changed

+17
-6
lines changed

src/qt/bitcoingui.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,10 @@ void BitcoinGUI::setWalletController(WalletController* wallet_controller)
899899

900900
GUIUtil::ExceptionSafeConnect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet);
901901
connect(wallet_controller, &WalletController::walletRemoved, this, &BitcoinGUI::removeWallet);
902+
connect(wallet_controller, &WalletController::destroyed, this, [this] {
903+
// wallet_controller gets destroyed manually, but it leaves our member copy dangling
904+
m_wallet_controller = nullptr;
905+
});
902906

903907
auto activity = new LoadWalletsActivity(m_wallet_controller, this);
904908
activity->load();
@@ -911,7 +915,7 @@ WalletController* BitcoinGUI::getWalletController()
911915

912916
void BitcoinGUI::addWallet(WalletModel* walletModel)
913917
{
914-
if (!walletFrame) return;
918+
if (!walletFrame || !m_wallet_controller) return;
915919

916920
WalletView* wallet_view = new WalletView(walletModel, walletFrame);
917921
if (!walletFrame->addView(wallet_view)) return;
@@ -959,7 +963,7 @@ void BitcoinGUI::removeWallet(WalletModel* walletModel)
959963

960964
void BitcoinGUI::setCurrentWallet(WalletModel* wallet_model)
961965
{
962-
if (!walletFrame) return;
966+
if (!walletFrame || !m_wallet_controller) return;
963967
walletFrame->setCurrentWallet(wallet_model);
964968
for (int index = 0; index < m_wallet_selector->count(); ++index) {
965969
if (m_wallet_selector->itemData(index).value<WalletModel*>() == wallet_model) {

src/qt/sendcoinsdialog.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -604,10 +604,15 @@ SendCoinsEntry *SendCoinsDialog::addEntry()
604604
entry->clear();
605605
entry->setFocus();
606606
ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
607-
qApp->processEvents();
608-
QScrollBar* bar = ui->scrollArea->verticalScrollBar();
609-
if(bar)
610-
bar->setSliderPosition(bar->maximum());
607+
608+
// Scroll to the newly added entry on a QueuedConnection because Qt doesn't
609+
// adjust the scroll area and scrollbar immediately when the widget is added.
610+
// Invoking on a DirectConnection will only scroll to the second-to-last entry.
611+
QMetaObject::invokeMethod(ui->scrollArea, [this] {
612+
if (ui->scrollArea->verticalScrollBar()) {
613+
ui->scrollArea->verticalScrollBar()->setValue(ui->scrollArea->verticalScrollBar()->maximum());
614+
}
615+
}, Qt::QueuedConnection);
611616

612617
updateTabsAndLabels();
613618
return entry;

src/qt/test/wallettests.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ void TestGUI(interfaces::Node& node)
163163
QCOMPARE(transactionTableModel->rowCount({}), 105);
164164
uint256 txid1 = SendCoins(*wallet.get(), sendCoinsDialog, PKHash(), 5 * COIN);
165165
uint256 txid2 = SendCoins(*wallet.get(), sendCoinsDialog, PKHash(), 10 * COIN);
166+
// Transaction table model updates on a QueuedConnection, so process events to ensure it's updated.
167+
qApp->processEvents();
166168
QCOMPARE(transactionTableModel->rowCount({}), 107);
167169
QVERIFY(FindTx(*transactionTableModel, txid1).isValid());
168170
QVERIFY(FindTx(*transactionTableModel, txid2).isValid());

0 commit comments

Comments
 (0)