Skip to content

Commit dfab5f3

Browse files
committed
Cleanup thread cleaning and fix PinPAD crash
Fixes #346 Signed-off-by: Raul Metsma <[email protected]>
1 parent a717afc commit dfab5f3

File tree

11 files changed

+115
-193
lines changed

11 files changed

+115
-193
lines changed

src/controller/controller.cpp

Lines changed: 55 additions & 120 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,56 +106,32 @@ 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);
112+
connect(waitForCardThread, &ControllerChildThread::failure, this,
113+
&Controller::onCriticalFailure);
115114
connect(waitForCardThread, &WaitForCardThread::statusUpdate, this, &Controller::statusUpdate);
116115
connect(waitForCardThread, &WaitForCardThread::cardsAvailable, this,
117116
&Controller::onCardsAvailable);
118-
saveChildThreadPtrAndConnectFailureFinish(waitForCardThread);
119117

120118
// UI setup.
121-
window = WebEidUI::createAndShowDialog(commandHandler->commandType());
122-
connect(this, &Controller::statusUpdate, window, &WebEidUI::onSmartCardStatusUpdate);
123-
connectOkCancelWaitingForPinPad();
119+
createWindow();
124120

125121
// Finally, start the thread to wait for card insertion after everything is wired up.
126122
waitForCardThread->start();
127123
}
128124

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-
156-
void Controller::connectOkCancelWaitingForPinPad()
125+
void Controller::createWindow()
157126
{
158-
REQUIRE_NON_NULL(window)
159-
127+
window = WebEidUI::createAndShowDialog(commandHandler->commandType());
128+
connect(this, &Controller::statusUpdate, window, &WebEidUI::onSmartCardStatusUpdate);
129+
connect(this, &Controller::retry, window, &WebEidUI::onRetry);
160130
connect(window, &WebEidUI::accepted, this, &Controller::onDialogOK);
161131
connect(window, &WebEidUI::rejected, this, &Controller::onDialogCancel);
162132
connect(window, &WebEidUI::failure, this, &Controller::onCriticalFailure);
163133
connect(window, &WebEidUI::waitingForPinPad, this, &Controller::onConfirmCommandHandler);
134+
connect(window, &WebEidUI::destroyed, this, [this] { window = nullptr; });
164135
}
165136

166137
void Controller::onCardsAvailable(
@@ -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,36 @@ 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(this, &Controller::stopCardEventMonitorThread, cardEventMonitorThread,
190+
&CardEventMonitorThread::requestInterruption);
191+
connect(cardEventMonitorThread, &ControllerChildThread::failure, this,
192+
&Controller::onCriticalFailure);
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
{
243199
if (window) {
244200
window->disconnect();
245-
// As the Qt::WA_DeleteOnClose flag is set, the dialog is deleted automatically.
246-
window->close();
201+
window->forceClose();
202+
window->deleteLater();
247203
window = nullptr;
248204
}
249205
}
250206

251207
void Controller::onConfirmCommandHandler(const EidCertificateAndPinInfo& certAndPinInfo)
252208
{
253-
stopCardEventMonitorThread();
209+
emit stopCardEventMonitorThread();
254210

255211
try {
256-
CommandHandlerConfirmThread* commandHandlerConfirmThread =
212+
auto* commandHandlerConfirmThread =
257213
new CommandHandlerConfirmThread(this, *commandHandler, window, certAndPinInfo);
258214
connect(commandHandlerConfirmThread, &CommandHandlerConfirmThread::completed, this,
259215
&Controller::onCommandHandlerConfirmCompleted);
260-
saveChildThreadPtrAndConnectFailureFinish(commandHandlerConfirmThread);
216+
connect(commandHandlerConfirmThread, &ControllerChildThread::failure, this,
217+
&Controller::onCriticalFailure);
261218
connectRetry(commandHandlerConfirmThread);
262219

263220
commandHandlerConfirmThread->start();
@@ -308,14 +265,9 @@ void Controller::onRetry()
308265
void Controller::connectRetry(const ControllerChildThread* childThread)
309266
{
310267
REQUIRE_NON_NULL(childThread)
311-
REQUIRE_NON_NULL(window)
312-
313-
disconnect(window, &WebEidUI::retry, nullptr, nullptr);
314-
315-
connect(childThread, &ControllerChildThread::retry, window, &WebEidUI::onRetry);
268+
connect(childThread, &ControllerChildThread::retry, this, &Controller::retry);
316269
// This connection handles cancel events from PIN pad.
317-
connect(childThread, &ControllerChildThread::cancel, this, &Controller::onPinPadCancel);
318-
connect(window, &WebEidUI::retry, this, &Controller::onRetry);
270+
connect(childThread, &ControllerChildThread::cancel, this, &Controller::onDialogCancel);
319271
}
320272

321273
void Controller::onDialogOK(const EidCertificateAndPinInfo& certAndPinInfo)
@@ -324,67 +276,50 @@ void Controller::onDialogOK(const EidCertificateAndPinInfo& certAndPinInfo)
324276
onConfirmCommandHandler(certAndPinInfo);
325277
} else {
326278
// This should not happen, and when it does, OK should be equivalent to cancel.
327-
onPinPadCancel();
279+
onDialogCancel();
328280
}
329281
}
330282

331283
void Controller::onDialogCancel()
332284
{
333-
REQUIRE_NON_NULL(window)
334-
335285
qDebug() << "User cancelled";
336-
337-
// Schedule application exit when the UI dialog is destroyed.
338-
connect(window, &WebEidUI::destroyed, this, &Controller::exit);
339-
340286
_result = makeErrorObject(RESP_USER_CANCEL, QStringLiteral("User cancelled"));
341287
writeResponseToStdOut(isInStdinMode, _result, commandType());
342-
}
343-
344-
void Controller::onPinPadCancel()
345-
{
346-
REQUIRE_NON_NULL(window)
347-
348-
onDialogCancel();
349-
window->quit();
288+
exit();
350289
}
351290

352291
void Controller::onCriticalFailure(const QString& error)
353292
{
293+
emit stopCardEventMonitorThread();
354294
qCritical() << "Exiting due to command" << std::string(commandType())
355295
<< "fatal error:" << error;
356296
_result =
357297
makeErrorObject(RESP_TECH_ERROR, QStringLiteral("Technical error, see application logs"));
358-
writeResponseToStdOut(isInStdinMode, _result, commandType());
359298
disposeUI();
360299
WebEidUI::showFatalError();
300+
// Write the error response to stdout before exiting. Chrome will close the application
301+
// immediately after this, so the UI dialog may not be visible to the user.
302+
writeResponseToStdOut(isInStdinMode, _result, commandType());
361303
exit();
362304
}
363305

364306
void Controller::exit()
365307
{
366-
if (window) {
367-
window->disconnect();
368-
window = nullptr;
369-
}
308+
disposeUI();
370309
waitForChildThreads();
371310
emit quit();
372311
}
373312

374313
void Controller::waitForChildThreads()
375314
{
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-
}
315+
for (auto* thread : findChildren<QThread*>()) {
316+
qDebug() << "Interrupting thread" << uintptr_t(thread);
317+
thread->disconnect();
318+
thread->requestInterruption();
319+
ControllerChildThread::waitForControllerNotify.wakeAll();
320+
while (thread->isRunning()) {
321+
using namespace std::chrono_literals;
322+
thread->wait(100ms);
388323
}
389324
}
390325
}

src/controller/controller.hpp

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,7 @@
2424

2525
#include "commandhandler.hpp"
2626

27-
#include <unordered_map>
28-
2927
class ControllerChildThread;
30-
class CardEventMonitorThread;
3128

3229
/** Controller coordinates the execution flow and interaction between all other components. */
3330
class Controller : public QObject
@@ -41,7 +38,9 @@ class Controller : public QObject
4138

4239
signals:
4340
void quit();
44-
void statusUpdate(const RetriableError status);
41+
void retry(const RetriableError error);
42+
void statusUpdate(RetriableError status);
43+
void stopCardEventMonitorThread();
4544

4645
public: // slots
4746
void run();
@@ -65,29 +64,21 @@ class Controller : public QObject
6564
void onDialogOK(const EidCertificateAndPinInfo& certAndPinInfo);
6665
void onDialogCancel();
6766

68-
// Called when user presses cancel on PIN pad.
69-
void onPinPadCancel();
70-
7167
// Failure handler, reports the error and quits the application.
7268
void onCriticalFailure(const QString& error);
7369

7470
private:
7571
void startCommandExecution();
7672
void runCommandHandler(const std::vector<electronic_id::ElectronicID::ptr>& availableEids);
77-
void connectOkCancelWaitingForPinPad();
7873
void connectRetry(const ControllerChildThread* childThread);
79-
void saveChildThreadPtrAndConnectFailureFinish(ControllerChildThread* childThread);
80-
void stopCardEventMonitorThread();
74+
void createWindow();
8175
void disposeUI();
8276
void exit();
8377
void waitForChildThreads();
8478
CommandType commandType();
8579

8680
CommandWithArgumentsPtr command;
8781
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;
9182
// As the Qt::WA_DeleteOnClose flag is set, the dialog is deleted automatically.
9283
observer_ptr<WebEidUI> window = nullptr;
9384
QVariantMap _result;

0 commit comments

Comments
 (0)