Skip to content

Commit 7defef0

Browse files
metsmamrts
authored andcommitted
Cleanup thread cleaning and fix PinPAD crash
Fixes #346 Signed-off-by: Raul Metsma <[email protected]>
1 parent 1a7fd4f commit 7defef0

File tree

11 files changed

+117
-195
lines changed

11 files changed

+117
-195
lines changed

src/controller/controller.cpp

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

3030
#include "utils/utils.hpp"
31+
32+
#include "application.hpp"
3133
#include "inputoutputmode.hpp"
3234
#include "writeresponse.hpp"
3335

34-
#include <QApplication>
35-
3636
using namespace pcsc_cpp;
3737
using namespace electronic_id;
3838

@@ -44,19 +44,11 @@ const QString RESP_USER_CANCEL = QStringLiteral("ERR_WEBEID_USER_CANCELLED");
4444

4545
QVariantMap makeErrorObject(const QString& errorCode, const QString& errorMessage)
4646
{
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();
47+
return {{QStringLiteral("error"),
48+
QVariantMap {
49+
{QStringLiteral("code"), errorCode},
50+
{QStringLiteral("message"), errorMessage},
51+
}}};
6052
}
6153

6254
} // namespace
@@ -67,17 +59,19 @@ void Controller::run()
6759
const bool isInCommandLineMode = bool(command);
6860
isInStdinMode = !isInCommandLineMode;
6961

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

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

8276
command = readCommandFromStdin();
8377
}
@@ -111,56 +105,33 @@ void Controller::startCommandExecution()
111105
REQUIRE_NON_NULL(commandHandler)
112106

113107
// Reader monitor thread setup.
114-
WaitForCardThread* waitForCardThread = new WaitForCardThread(this);
108+
auto* waitForCardThread = new WaitForCardThread(this);
109+
connect(this, &Controller::stopCardEventMonitorThread, waitForCardThread,
110+
&WaitForCardThread::requestInterruption);
111+
connect(waitForCardThread, &ControllerChildThread::failure, this,
112+
&Controller::onCriticalFailure);
115113
connect(waitForCardThread, &WaitForCardThread::statusUpdate, this, &Controller::statusUpdate);
116114
connect(waitForCardThread, &WaitForCardThread::cardsAvailable, this,
117115
&Controller::onCardsAvailable);
118-
saveChildThreadPtrAndConnectFailureFinish(waitForCardThread);
119116

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

125120
// Finally, start the thread to wait for card insertion after everything is wired up.
126121
waitForCardThread->start();
127122
}
128123

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()
124+
void Controller::createWindow()
157125
{
158-
REQUIRE_NON_NULL(window)
159-
126+
window = WebEidUI::createAndShowDialog(commandHandler->commandType());
127+
connect(this, &Controller::statusUpdate, window, &WebEidUI::onSmartCardStatusUpdate);
128+
connect(this, &Controller::retry, window, &WebEidUI::onRetry);
129+
connect(window, &WebEidUI::retry, this, &Controller::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,8 @@ 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);
198168
connectRetry(commandHandlerRunThread);
199169

200170
// When the command handler run thread retrieves certificates successfully, call
@@ -213,51 +183,34 @@ void Controller::runCommandHandler(const std::vector<ElectronicID::ptr>& availab
213183

214184
void Controller::onCertificatesLoaded()
215185
{
216-
CardEventMonitorThread* cardEventMonitorThread =
217-
new CardEventMonitorThread(this, std::string(commandType()));
218-
saveChildThreadPtrAndConnectFailureFinish(cardEventMonitorThread);
219-
cardEventMonitorThreadKey = uintptr_t(cardEventMonitorThread);
186+
auto* cardEventMonitorThread = new CardEventMonitorThread(this, commandType());
187+
connect(this, &Controller::stopCardEventMonitorThread, cardEventMonitorThread,
188+
&CardEventMonitorThread::requestInterruption);
189+
connect(cardEventMonitorThread, &ControllerChildThread::failure, this,
190+
&Controller::onCriticalFailure);
220191
connect(cardEventMonitorThread, &CardEventMonitorThread::cardEvent, this, &Controller::onRetry);
221192
cardEventMonitorThread->start();
222193
}
223194

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-
241195
void Controller::disposeUI()
242196
{
243197
if (window) {
244198
window->disconnect();
245-
// As the Qt::WA_DeleteOnClose flag is set, the dialog is deleted automatically.
246-
window->close();
199+
window->forceClose();
200+
window->deleteLater();
247201
window = nullptr;
248202
}
249203
}
250204

251205
void Controller::onConfirmCommandHandler(const EidCertificateAndPinInfo& certAndPinInfo)
252206
{
253-
stopCardEventMonitorThread();
207+
emit stopCardEventMonitorThread();
254208

255209
try {
256-
CommandHandlerConfirmThread* commandHandlerConfirmThread =
210+
auto* commandHandlerConfirmThread =
257211
new CommandHandlerConfirmThread(this, *commandHandler, window, certAndPinInfo);
258212
connect(commandHandlerConfirmThread, &CommandHandlerConfirmThread::completed, this,
259213
&Controller::onCommandHandlerConfirmCompleted);
260-
saveChildThreadPtrAndConnectFailureFinish(commandHandlerConfirmThread);
261214
connectRetry(commandHandlerConfirmThread);
262215

263216
commandHandlerConfirmThread->start();
@@ -308,14 +261,10 @@ void Controller::onRetry()
308261
void Controller::connectRetry(const ControllerChildThread* childThread)
309262
{
310263
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);
264+
connect(childThread, &ControllerChildThread::failure, this, &Controller::onCriticalFailure);
265+
connect(childThread, &ControllerChildThread::retry, this, &Controller::retry);
316266
// This connection handles cancel events from PIN pad.
317-
connect(childThread, &ControllerChildThread::cancel, this, &Controller::onPinPadCancel);
318-
connect(window, &WebEidUI::retry, this, &Controller::onRetry);
267+
connect(childThread, &ControllerChildThread::cancel, this, &Controller::onDialogCancel);
319268
}
320269

321270
void Controller::onDialogOK(const EidCertificateAndPinInfo& certAndPinInfo)
@@ -324,68 +273,54 @@ void Controller::onDialogOK(const EidCertificateAndPinInfo& certAndPinInfo)
324273
onConfirmCommandHandler(certAndPinInfo);
325274
} else {
326275
// This should not happen, and when it does, OK should be equivalent to cancel.
327-
onPinPadCancel();
276+
onDialogCancel();
328277
}
329278
}
330279

331280
void Controller::onDialogCancel()
332281
{
333-
REQUIRE_NON_NULL(window)
334-
335282
qDebug() << "User cancelled";
336-
337-
// Schedule application exit when the UI dialog is destroyed.
338-
connect(window, &WebEidUI::destroyed, this, &Controller::exit);
339-
340283
_result = makeErrorObject(RESP_USER_CANCEL, QStringLiteral("User cancelled"));
341284
writeResponseToStdOut(isInStdinMode, _result, commandType());
342-
}
343-
344-
void Controller::onPinPadCancel()
345-
{
346-
REQUIRE_NON_NULL(window)
347-
348-
onDialogCancel();
349-
window->quit();
285+
exit();
350286
}
351287

352288
void Controller::onCriticalFailure(const QString& error)
353289
{
290+
emit stopCardEventMonitorThread();
354291
qCritical() << "Exiting due to command" << std::string(commandType())
355292
<< "fatal error:" << error;
356293
_result =
357294
makeErrorObject(RESP_TECH_ERROR, QStringLiteral("Technical error, see application logs"));
358-
writeResponseToStdOut(isInStdinMode, _result, commandType());
359295
disposeUI();
296+
if (qApp->isSafariExtensionContainingApp()) {
297+
writeResponseToStdOut(isInStdinMode, _result, commandType());
298+
}
360299
WebEidUI::showFatalError();
300+
if (!qApp->isSafariExtensionContainingApp()) {
301+
// Write the error response to stdout after showing the fatal error dialog. Chrome will
302+
// close the application immediately after this, so the UI dialog may not be visible to the
303+
// user.
304+
writeResponseToStdOut(isInStdinMode, _result, commandType());
305+
}
361306
exit();
362307
}
363308

364309
void Controller::exit()
365310
{
366-
if (window) {
367-
window->disconnect();
368-
window = nullptr;
369-
}
311+
disposeUI();
370312
waitForChildThreads();
371313
emit quit();
372314
}
373315

374316
void Controller::waitForChildThreads()
375317
{
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-
}
388-
}
318+
for (auto* thread : findChildren<QThread*>()) {
319+
qDebug() << "Interrupting thread" << uintptr_t(thread);
320+
thread->disconnect();
321+
thread->requestInterruption();
322+
ControllerChildThread::waitForControllerNotify.wakeAll();
323+
thread->wait();
389324
}
390325
}
391326

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)