This updates the analysis and replaces the debdiff in comment #2. Please
use the patch attached here. Nothing has been uploaded, so the earlier
debdiff can be replaced without a follow-up upload.
The blocking call, measurement method and impact are unchanged. In the
Unix backend, isKwalletAvailable() in qtkeychain/keychain_unix.cpp makes
the only synchronous D-Bus call. All other D-Bus paths use
QDBusPendingCallWatcher. This call blocks the calling thread, which is
the GUI thread for most callers. The delay of about 25 seconds, the
heartbeat results and the effect on plasmashell still reproduce.
I got the trigger wrong in the original report. A locked wallet alone
does not delay replies. networkWallet() reads the configuration and
returns immediately even when the wallet is locked:
$ dbus-send --session --print-reply --dest=org.kde.kwalletd6 \
/modules/kwalletd6 org.kde.KWallet.isOpen string:kdewallet
boolean false
$ time dbus-send --session --print-reply --dest=org.kde.kwalletd6 \
/modules/kwalletd6 org.kde.KWallet.networkWallet
string "kdewallet"
real 0m0.003s
kwalletd stops answering application-level D-Bus calls while an unlock
dialog is open for any client. The probe waits for another application's
dialog to close. At login, several autostarted applications may request
the wallet at once, which explains why the original reproduction worked.
This also rules out the fix I first proposed.
I measured this on Kubuntu 26.10 with kwalletd6 4:26.08.0-0ubuntu1 and
libqt6keychain1 0.17.0-1ubuntu1. Both packages were unmodified, with
dpkg -V clean. With the wallet locked and an unlock dialog opened by a
separate client:
org.freedesktop.DBus.NameHasOwner 0 ms true
org.freedesktop.DBus.ListActivatableNames 0 ms contains kwalletd6
org.freedesktop.DBus.Peer.Ping 0 ms reply
org.kde.KWallet.networkWallet 25301 ms NoReply
In the same program, a Qt event loop with a heartbeat every 100 ms had a
longest gap of 25305 ms between ticks. That is 4 ms longer than the call
itself, accounting for the measured freeze.
I also tested with kwalletd stopped:
$ killall kwalletd6
$ dbus-send ... NameHasOwner string:org.kde.kwalletd6 -> boolean false
$ time dbus-send ... org.freedesktop.DBus.Peer.Ping -> reply, 0m0.072s
$ dbus-send ... NameHasOwner string:org.kde.kwalletd6 -> boolean true
Ping activated kwalletd and returned in 72 ms. It therefore preserves
the case described in the existing isKwalletAvailable() comment, where
KWallet is installed but not running.
To reproduce:
1. On Kubuntu 26.10, set the KWallet password to something other than
the login password, then reboot. If the passwords match, kwallet-pam
opens the wallet at login and prevents this reproduction.
2. Confirm that the wallet is locked:
dbus-send --session --print-reply --dest=org.kde.kwalletd6 \
/modules/kwalletd6 org.kde.KWallet.isOpen string:kdewallet
Expect boolean false.
3. In Terminal 1, open an unlock dialog and leave it open. This
represents the first application to request the wallet at login:
dbus-send --session --print-reply --reply-timeout=600000 \
--dest=org.kde.kwalletd6 /modules/kwalletd6 \
org.kde.KWallet.open string:kdewallet int64:0 string:trigger
4. In Terminal 2, while the dialog is open, run:
time dbus-send --session --print-reply --reply-timeout=30000 \
--dest=org.kde.kwalletd6 /modules/kwalletd6 \
org.kde.KWallet.networkWallet
time dbus-send --session --print-reply \
--dest=org.kde.kwalletd6 /modules/kwalletd6 \
org.freedesktop.DBus.Peer.Ping
networkWallet times out with NoReply. Ping answers in single-digit
milliseconds.
5. To reproduce the application freeze, build the following program
against the archive package. It starts a heartbeat before a single
ReadPasswordJob, so you can distinguish a blocked thread from a job that
is still waiting for a reply:
// kwallet-freeze.cpp
#include <QCoreApplication>
#include <QElapsedTimer>
#include <QTextStream>
#include <QTimer>
#include <qt6keychain/keychain.h>
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
QTextStream out(stdout);
QElapsedTimer clock;
clock.start();
QTimer heartbeat;
heartbeat.setInterval(500);
QObject::connect(&heartbeat, &QTimer::timeout, &app,
[&out, &clock] { out << "tick " << clock.elapsed() << " ms" <<
Qt::endl; });
heartbeat.start();
auto *job = new
QKeychain::ReadPasswordJob(QStringLiteral("freeze-probe"), &app);
job->setAutoDelete(false);
job->setInsecureFallback(false);
job->setKey(QStringLiteral("any-key"));
QObject::connect(job, &QKeychain::Job::finished, &app,
[&out, &clock, &app, job](QKeychain::Job *) {
out << "job finished, elapsed " << clock.elapsed()
<< " ms (error " << int(job->error()) << ")" << Qt::endl;
app.quit();
});
job->start();
return app.exec();
}
Install qt6-base-dev and qtkeychain-qt6-dev, then build:
g++ -fPIC -std=c++17 kwallet-freeze.cpp -o kwallet-freeze \
$(pkg-config --cflags --libs Qt6Core) -lqt6keychain
Run ./kwallet-freeze in Terminal 2 while the dialog from step 3 is
open. With the stock package, the heartbeat does not tick for about 25
seconds. With the attached patch, it ticks every 500 ms throughout, and
the job proceeds without blocking the thread.
The attached patch changes the probe from networkWallet() to
org.freedesktop.DBus.Peer.Ping. The QtDBus layer in kwalletd answers
Ping before application code handles the call, so an open unlock dialog
does not delay the reply. Ping also triggers D-Bus activation, as the
test above shows. The patch has 18 insertions and 8 deletions, all in
isKwalletAvailable().
I have proposed it upstream in PR #304, replacing the earlier approach in that
PR:
https://github.com/frankosterfeld/qtkeychain/pull/304
isKwalletAvailable() is byte-identical in the 0.17.0 tag and current
upstream main, so the patch applies to both. The distro patch can be
dropped once the upstream change is included.
The timeout cap from comment #2 still freezes the GUI. Under the same
conditions:
networkWallet with a 5 s cap 4980 ms NoReply -> "available"
That version waits nearly five seconds, then reports KWallet as
available regardless of the reply. The wait only provides information if
a slow error reply arrives, which is not the case here.
Capping the timeout also caused upstream issue #242. A slow kwalletd was
treated as absent, and secrets were written to a different backend. Ping
avoids depending on kwalletd's application code being idle.
I expect the regression risk to be low. The change affects one function
that checks whether KWallet is present and probes the same service
through a lower-level call. The case to watch is a service that does not
answer org.freedesktop.DBus.Peer.Ping. kwalletd is Qt-based and handles
Ping in the QtDBus layer. The tests above cover it both while an unlock
dialog is open and when D-Bus must start it.
The source package is lintian-clean, and I have run update-maintainer. I
am still subscribing ubuntu-sponsors.
--
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/2165214
Title:
libqt6keychain1 0.17.0-1: applications block for 25 seconds when the
KWallet is locked
To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/qtkeychain/+bug/2165214/+subscriptions
--
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs