Skip to content

Commit 2911ccf

Browse files
committed
Cleanup thread cleaning and fix PinPAD crash
Fixes #346 Signed-off-by: Raul Metsma <raul@metsma.ee>
1 parent a717afc commit 2911ccf

File tree

7 files changed

+87
-154
lines changed

7 files changed

+87
-154
lines changed

src/controller/controller.cpp

Lines changed: 42 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@
2828
#include "threads/waitforcardthread.hpp"
2929

3030
#include "utils/utils.hpp"
31+
3132
#include "inputoutputmode.hpp"
3233
#include "writeresponse.hpp"
3334

34-
#include <QApplication>
35+
#include <QCoreApplication>
3536

3637
using namespace pcsc_cpp;
3738
using namespace electronic_id;
@@ -44,19 +45,11 @@ const QString RESP_USER_CANCEL = QStringLiteral("ERR_WEBEID_USER_CANCELLED");
4445

4546
QVariantMap makeErrorObject(const QString& errorCode, const QString& errorMessage)
4647
{
47-
const auto errorBody = QVariantMap {
48-
{QStringLiteral("code"), errorCode},
49-
{QStringLiteral("message"), errorMessage},
50-
};
51-
return {{QStringLiteral("error"), errorBody}};
52-
}
53-
54-
void interruptThread(QThread* thread)
55-
{
56-
qDebug() << "Interrupting thread" << uintptr_t(thread);
57-
thread->disconnect();
58-
thread->requestInterruption();
59-
ControllerChildThread::waitForControllerNotify.wakeAll();
48+
return {{QStringLiteral("error"),
49+
QVariantMap {
50+
{QStringLiteral("code"), errorCode},
51+
{QStringLiteral("message"), errorMessage},
52+
}}};
6053
}
6154

6255
} // namespace
@@ -67,17 +60,19 @@ void Controller::run()
6760
const bool isInCommandLineMode = bool(command);
6861
isInStdinMode = !isInCommandLineMode;
6962

70-
qInfo() << qApp->applicationName() << "app" << qApp->applicationVersion() << "running in"
63+
qInfo() << QCoreApplication::applicationName() << "app"
64+
<< QCoreApplication::applicationVersion() << "running in"
7165
<< (isInStdinMode ? "stdin/stdout" : "command-line") << "mode";
7266

7367
try {
7468
// TODO: cut out stdin mode separate class to avoid bugs in safari mode
7569
if (isInStdinMode) {
7670
// In stdin/stdout mode we first output the version as required by the WebExtension
7771
// and then wait for the actual command.
78-
writeResponseToStdOut(isInStdinMode,
79-
{{QStringLiteral("version"), qApp->applicationVersion()}},
80-
"get-version");
72+
writeResponseToStdOut(
73+
isInStdinMode,
74+
{{QStringLiteral("version"), QCoreApplication::applicationVersion()}},
75+
"get-version");
8176

8277
command = readCommandFromStdin();
8378
}
@@ -111,11 +106,14 @@ void Controller::startCommandExecution()
111106
REQUIRE_NON_NULL(commandHandler)
112107

113108
// Reader monitor thread setup.
114-
WaitForCardThread* waitForCardThread = new WaitForCardThread(this);
109+
auto* waitForCardThread = new WaitForCardThread(this);
110+
connect(this, &Controller::stopCardEventMonitorThread, waitForCardThread,
111+
&WaitForCardThread::requestInterruption);
115112
connect(waitForCardThread, &WaitForCardThread::statusUpdate, this, &Controller::statusUpdate);
116113
connect(waitForCardThread, &WaitForCardThread::cardsAvailable, this,
117114
&Controller::onCardsAvailable);
118-
saveChildThreadPtrAndConnectFailureFinish(waitForCardThread);
115+
connect(waitForCardThread, &ControllerChildThread::failure, this,
116+
&Controller::onCriticalFailure);
119117

120118
// UI setup.
121119
window = WebEidUI::createAndShowDialog(commandHandler->commandType());
@@ -126,33 +124,6 @@ void Controller::startCommandExecution()
126124
waitForCardThread->start();
127125
}
128126

129-
void Controller::saveChildThreadPtrAndConnectFailureFinish(ControllerChildThread* childThread)
130-
{
131-
REQUIRE_NON_NULL(childThread)
132-
// Save the thread pointer in child thread tracking map to request interruption and wait for
133-
// it to quit in waitForChildThreads().
134-
childThreads[uintptr_t(childThread)] = childThread;
135-
136-
connect(childThread, &ControllerChildThread::failure, this, &Controller::onCriticalFailure);
137-
138-
// When the thread is finished, remove the pointer from the tracking map and call deleteLater()
139-
// on it to free the thread object. Although the thread objects are freed through the Qt object
140-
// tree ownership system anyway, it is better to delete them immediately when they finish.
141-
connect(childThread, &ControllerChildThread::finished, this, [this, childThread]() {
142-
QScopedPointer<ControllerChildThread, QScopedPointerDeleteLater> deleteLater {childThread};
143-
144-
const auto threadPtrAddress = uintptr_t(childThread);
145-
if (childThreads.count(threadPtrAddress) && childThreads[threadPtrAddress]) {
146-
childThreads[threadPtrAddress] = nullptr;
147-
childThread->wait();
148-
qDebug() << "Thread" << threadPtrAddress << "finished";
149-
} else {
150-
qWarning() << "Controller child thread" << childThread
151-
<< "is missing or null in finish slot";
152-
}
153-
});
154-
}
155-
156127
void Controller::connectOkCancelWaitingForPinPad()
157128
{
158129
REQUIRE_NON_NULL(window)
@@ -192,9 +163,10 @@ void Controller::onCardsAvailable(
192163
void Controller::runCommandHandler(const std::vector<ElectronicID::ptr>& availableEids)
193164
{
194165
try {
195-
CommandHandlerRunThread* commandHandlerRunThread =
166+
auto* commandHandlerRunThread =
196167
new CommandHandlerRunThread(this, *commandHandler, availableEids);
197-
saveChildThreadPtrAndConnectFailureFinish(commandHandlerRunThread);
168+
connect(commandHandlerRunThread, &ControllerChildThread::failure, this,
169+
&Controller::onCriticalFailure);
198170
connectRetry(commandHandlerRunThread);
199171

200172
// When the command handler run thread retrieves certificates successfully, call
@@ -213,51 +185,32 @@ void Controller::runCommandHandler(const std::vector<ElectronicID::ptr>& availab
213185

214186
void Controller::onCertificatesLoaded()
215187
{
216-
CardEventMonitorThread* cardEventMonitorThread =
217-
new CardEventMonitorThread(this, std::string(commandType()));
218-
saveChildThreadPtrAndConnectFailureFinish(cardEventMonitorThread);
219-
cardEventMonitorThreadKey = uintptr_t(cardEventMonitorThread);
188+
auto* cardEventMonitorThread = new CardEventMonitorThread(this, commandType());
189+
connect(cardEventMonitorThread, &ControllerChildThread::failure, this,
190+
&Controller::onCriticalFailure);
191+
connect(this, &Controller::stopCardEventMonitorThread, cardEventMonitorThread,
192+
&WaitForCardThread::requestInterruption);
220193
connect(cardEventMonitorThread, &CardEventMonitorThread::cardEvent, this, &Controller::onRetry);
221194
cardEventMonitorThread->start();
222195
}
223196

224-
void Controller::stopCardEventMonitorThread()
225-
{
226-
if (cardEventMonitorThreadKey) {
227-
try {
228-
auto cardEventMonitorThread = childThreads.at(cardEventMonitorThreadKey);
229-
cardEventMonitorThreadKey = 0;
230-
if (cardEventMonitorThread) {
231-
interruptThread(cardEventMonitorThread);
232-
}
233-
} catch (const std::out_of_range&) {
234-
qWarning() << "Card event monitor thread" << cardEventMonitorThreadKey
235-
<< "is missing from childThreads map in stopCardEventMonitorThread()";
236-
cardEventMonitorThreadKey = 0;
237-
}
238-
}
239-
}
240-
241197
void Controller::disposeUI()
242198
{
243-
if (window) {
244-
window->disconnect();
245-
// As the Qt::WA_DeleteOnClose flag is set, the dialog is deleted automatically.
246-
window->close();
247-
window = nullptr;
248-
}
199+
delete window;
200+
window = nullptr;
249201
}
250202

251203
void Controller::onConfirmCommandHandler(const EidCertificateAndPinInfo& certAndPinInfo)
252204
{
253-
stopCardEventMonitorThread();
205+
emit stopCardEventMonitorThread();
254206

255207
try {
256-
CommandHandlerConfirmThread* commandHandlerConfirmThread =
208+
auto* commandHandlerConfirmThread =
257209
new CommandHandlerConfirmThread(this, *commandHandler, window, certAndPinInfo);
258210
connect(commandHandlerConfirmThread, &CommandHandlerConfirmThread::completed, this,
259211
&Controller::onCommandHandlerConfirmCompleted);
260-
saveChildThreadPtrAndConnectFailureFinish(commandHandlerConfirmThread);
212+
connect(commandHandlerConfirmThread, &ControllerChildThread::failure, this,
213+
&Controller::onCriticalFailure);
261214
connectRetry(commandHandlerConfirmThread);
262215

263216
commandHandlerConfirmThread->start();
@@ -351,6 +304,7 @@ void Controller::onPinPadCancel()
351304

352305
void Controller::onCriticalFailure(const QString& error)
353306
{
307+
emit stopCardEventMonitorThread();
354308
qCritical() << "Exiting due to command" << std::string(commandType())
355309
<< "fatal error:" << error;
356310
_result =
@@ -373,18 +327,15 @@ void Controller::exit()
373327

374328
void Controller::waitForChildThreads()
375329
{
376-
// Waiting for child threads must not happen in destructor.
377-
// See https://tombarta.wordpress.com/2008/07/10/gcc-pure-virtual-method-called/ for details.
378-
for (const auto& childThread : childThreads) {
379-
auto thread = childThread.second;
380-
if (thread) {
381-
interruptThread(thread);
382-
// Waiting for PIN input on PIN pad may take a long time, call processEvents() so that
383-
// the UI doesn't freeze.
384-
while (thread->isRunning()) {
385-
thread->wait(100); // in milliseconds
386-
QCoreApplication::processEvents();
387-
}
330+
for (auto* thread : findChildren<QThread*>()) {
331+
qDebug() << "Interrupting thread" << uintptr_t(thread);
332+
thread->disconnect();
333+
thread->requestInterruption();
334+
ControllerChildThread::waitForControllerNotify.wakeAll();
335+
// Call processEvents() so that the UI doesn't freeze.
336+
while (thread->isRunning()) {
337+
thread->wait(100ms);
338+
QCoreApplication::processEvents();
388339
}
389340
}
390341
}

src/controller/controller.hpp

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ class Controller : public QObject
4141

4242
signals:
4343
void quit();
44-
void statusUpdate(const RetriableError status);
44+
void statusUpdate(RetriableError status);
45+
void stopCardEventMonitorThread();
4546

4647
public: // slots
4748
void run();
@@ -76,18 +77,13 @@ class Controller : public QObject
7677
void runCommandHandler(const std::vector<electronic_id::ElectronicID::ptr>& availableEids);
7778
void connectOkCancelWaitingForPinPad();
7879
void connectRetry(const ControllerChildThread* childThread);
79-
void saveChildThreadPtrAndConnectFailureFinish(ControllerChildThread* childThread);
80-
void stopCardEventMonitorThread();
8180
void disposeUI();
8281
void exit();
8382
void waitForChildThreads();
8483
CommandType commandType();
8584

8685
CommandWithArgumentsPtr command;
8786
CommandHandler::ptr commandHandler = nullptr;
88-
std::unordered_map<uintptr_t, observer_ptr<ControllerChildThread>> childThreads;
89-
// Key of card event monitor thread in childThreads map.
90-
uintptr_t cardEventMonitorThreadKey = 0;
9187
// As the Qt::WA_DeleteOnClose flag is set, the dialog is deleted automatically.
9288
observer_ptr<WebEidUI> window = nullptr;
9389
QVariantMap _result;

src/controller/threads/cardeventmonitorthread.hpp

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,23 @@ class CardEventMonitorThread : public ControllerChildThread
3333
using eid_ptr_vector = std::vector<electronic_id::ElectronicID::ptr>;
3434

3535
CardEventMonitorThread(QObject* parent, std::string commandType) :
36-
ControllerChildThread(parent), cmdType(std::move(commandType))
36+
ControllerChildThread(std::move(commandType), parent)
3737
{
3838
}
3939

4040
void run() override
4141
{
4242
QMutexLocker lock {&controllerChildThreadMutex};
4343

44-
beforeRun();
44+
qDebug() << "Starting" << metaObject()->className() << uintptr_t(this) << "for command"
45+
<< commandType();
4546

4647
auto initialCards = getSupportedCardsIgnoringExceptions();
4748
sortByReaderNameAndAtr(initialCards);
4849

4950
while (!isInterruptionRequested()) {
5051

51-
waitForControllerNotify.wait(&controllerChildThreadMutex, ONE_SECOND);
52+
waitForControllerNotify.wait(&controllerChildThreadMutex, 1s);
5253

5354
eid_ptr_vector updatedCards {};
5455

@@ -57,7 +58,8 @@ class CardEventMonitorThread : public ControllerChildThread
5758
sortByReaderNameAndAtr(updatedCards);
5859
} catch (const std::exception& error) {
5960
// Ignore smart card layer errors, they will be handled during next card operation.
60-
qWarning() << className << "ignoring" << commandType() << "error:" << error;
61+
qWarning() << metaObject()->className() << "ignoring" << commandType()
62+
<< "error:" << error;
6163
}
6264

6365
// If interruption was requested during wait, exit without emitting.
@@ -67,7 +69,7 @@ class CardEventMonitorThread : public ControllerChildThread
6769

6870
// If there was a change in connected supported cards, exit after emitting a card event.
6971
if (!areEqualByReaderNameAndAtr(initialCards, updatedCards)) {
70-
qDebug() << className << "card change detected";
72+
qDebug() << metaObject()->className() << "card change detected";
7173
emit cardEvent();
7274
return;
7375
}
@@ -90,9 +92,10 @@ class CardEventMonitorThread : public ControllerChildThread
9092
return electronic_id::availableSupportedCards();
9193
} catch (const std::exception& error) {
9294
// Ignore smart card layer errors, they will be handled during next card operation.
93-
qWarning() << className << "ignoring" << commandType() << "error:" << error;
95+
qWarning() << metaObject()->className() << "ignoring" << commandType()
96+
<< "error:" << error;
9497
}
95-
waitForControllerNotify.wait(&controllerChildThreadMutex, ONE_SECOND);
98+
waitForControllerNotify.wait(&controllerChildThreadMutex, 1s);
9699
}
97100
// Interruption was requested, return empty list.
98101
return {};
@@ -110,16 +113,10 @@ class CardEventMonitorThread : public ControllerChildThread
110113

111114
static bool areEqualByReaderNameAndAtr(const eid_ptr_vector& a, const eid_ptr_vector& b)
112115
{
113-
// std::equal requires that second range is not shorter than first, so compare size first.
114-
return a.size() == b.size()
115-
&& std::equal(a.cbegin(), a.cend(), b.cbegin(),
116+
return std::equal(a.cbegin(), a.cend(), b.cbegin(), b.cend(),
116117
[](const eid_ptr& c1, const eid_ptr& c2) {
117118
return c1->smartcard().readerName() == c2->smartcard().readerName()
118119
&& c1->smartcard().atr() == c2->smartcard().atr();
119120
});
120121
}
121-
122-
const std::string& commandType() const override { return cmdType; }
123-
124-
const std::string cmdType;
125122
};

src/controller/threads/commandhandlerconfirmthread.hpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ class CommandHandlerConfirmThread : public ControllerChildThread
3131
public:
3232
CommandHandlerConfirmThread(QObject* parent, CommandHandler& handler, WebEidUI* w,
3333
const EidCertificateAndPinInfo& certAndPin) :
34-
ControllerChildThread(parent), commandHandler(handler),
35-
cmdType(commandHandler.commandType()), window(w), certAndPinInfo(certAndPin)
34+
ControllerChildThread(handler.commandType(), parent), commandHandler(handler), window(w),
35+
certAndPinInfo(certAndPin)
3636
{
3737
}
3838

@@ -47,10 +47,7 @@ class CommandHandlerConfirmThread : public ControllerChildThread
4747
emit completed(result);
4848
}
4949

50-
const std::string& commandType() const override { return cmdType; }
51-
5250
CommandHandler& commandHandler;
53-
const std::string cmdType;
5451
WebEidUI* window;
5552
EidCertificateAndPinInfo certAndPinInfo;
5653
};

src/controller/threads/commandhandlerrunthread.hpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ class CommandHandlerRunThread : public ControllerChildThread
3131
public:
3232
CommandHandlerRunThread(QObject* parent, CommandHandler& handler,
3333
const std::vector<electronic_id::ElectronicID::ptr>& eids) :
34-
ControllerChildThread(parent), commandHandler(handler),
35-
cmdType(commandHandler.commandType()), eids(eids)
34+
ControllerChildThread(handler.commandType(), parent), commandHandler(handler), eids(eids)
3635
{
3736
// Connect retry signal to retry signal to pass it up from the command handler.
3837
connect(&commandHandler, &CommandHandler::retry, this, &ControllerChildThread::retry);
@@ -41,9 +40,6 @@ class CommandHandlerRunThread : public ControllerChildThread
4140
private:
4241
void doRun() override { commandHandler.run(eids); }
4342

44-
const std::string& commandType() const override { return cmdType; }
45-
4643
CommandHandler& commandHandler;
47-
const std::string cmdType;
4844
std::vector<electronic_id::ElectronicID::ptr> eids;
4945
};

0 commit comments

Comments
 (0)