Skip to content
This repository was archived by the owner on Mar 4, 2023. It is now read-only.

Commit b1622f8

Browse files
committed
WIP fix exiting...
1 parent f11fb12 commit b1622f8

File tree

4 files changed

+171
-81
lines changed

4 files changed

+171
-81
lines changed

src/datasync/exchangeengine.cpp

Lines changed: 103 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
#include "syncmanager_p.h"
1010
#include "changeemitter_p.h"
1111

12-
#include <QtCore/QThread>
12+
#include <QtCore/QDebug>
13+
#include <QtCore/QCoreApplication>
1314

1415
using namespace QtDataSync;
1516

@@ -26,6 +27,11 @@ ExchangeEngine::ExchangeEngine(const QString &setupName, Setup::FatalErrorHandle
2627
_emitter{new ChangeEmitter(_defaults, this)} //must be created here, because of access
2728
{}
2829

30+
ExchangeEngine::~ExchangeEngine()
31+
{
32+
logDebug() << "Finalization completed";
33+
}
34+
2935
void ExchangeEngine::enterFatalState(const QString &error, const char *file, int line, const char *function, const char *category)
3036
{
3137
QMessageLogContext context {file, line, function, category};
@@ -168,10 +174,8 @@ void ExchangeEngine::finalize()
168174

169175
//remoteconnector is the only one asynchronous (for now)
170176
connect(_remoteConnector, &RemoteConnector::finalized,
171-
this, [this](){
172-
logDebug() << "Finalization completed";
173-
thread()->quit();
174-
});
177+
thread(), &QThread::quit,
178+
Qt::DirectConnection); //TODO unsafe?
175179

176180
_syncController->finalize();
177181
_changeController->finalize();
@@ -335,3 +339,97 @@ ExchangeEngine::ImportData::ImportData(QJsonObject data, QString password, bool
335339
keepData{keepData},
336340
allowFailure{allowFailure}
337341
{}
342+
343+
// ------------- Engine Thread -------------
344+
345+
EngineThread::EngineThread(QString setupName, ExchangeEngine *engine, QLockFile *lockFile) :
346+
_name{std::move(setupName)},
347+
_engine{engine},
348+
_lockFile{lockFile}
349+
{
350+
setTerminationEnabled(true);
351+
_engine.load()->moveToThread(this);
352+
353+
connect(thread(), &QThread::finished,
354+
this, &EngineThread::stopSelf);
355+
//TODO dangerous? find better way
356+
// connect(this, &QThread::finished,
357+
// this, &QThread::deleteLater);
358+
}
359+
360+
EngineThread::~EngineThread()
361+
{
362+
Q_ASSERT_X(!isRunning(), Q_FUNC_INFO, "Engine thread destroyed while engine is still running");
363+
}
364+
365+
QString EngineThread::name() const
366+
{
367+
return _name;
368+
}
369+
370+
ExchangeEngine *EngineThread::engine() const
371+
{
372+
return _engine.load();
373+
}
374+
375+
bool EngineThread::isRunning() const
376+
{
377+
return _running;
378+
}
379+
380+
bool EngineThread::startEngine()
381+
{
382+
if(!_running.testAndSetRelaxed(false, true))
383+
return false;
384+
start();
385+
return true;
386+
}
387+
388+
bool EngineThread::stopEngine()
389+
{
390+
if(!_running.testAndSetRelaxed(true, false))
391+
return false;
392+
qCDebug(qdssetup) << "Finalizing engine of setup" << _name;
393+
QMetaObject::invokeMethod(_engine, "finalize", Qt::QueuedConnection);
394+
return true;
395+
}
396+
397+
void EngineThread::waitAndTerminate(unsigned long timeout)
398+
{
399+
if(!isRunning())
400+
return;
401+
if(!wait(timeout)) {
402+
qCWarning(qdssetup) << "Workerthread of setup" << _name << "did not finish before the timout. Terminating...";
403+
terminate();
404+
auto wRes = wait(500);
405+
qCDebug(qdssetup) << "Terminate result for setup" << _name << ":"
406+
<< wRes;
407+
}
408+
}
409+
410+
void EngineThread::run()
411+
{
412+
try {
413+
qCDebug(qdssetup) << "Engine thread for setup" << _name << "has started";
414+
_engine.load()->initialize();
415+
exec();
416+
_running = false;
417+
delete _engine;
418+
_lockFile->unlock();
419+
delete _lockFile;
420+
qCDebug(qdssetup) << "Engine thread for setup" << _name << "has stopped";
421+
} catch(QException &e) {
422+
if(_lockFile)
423+
_lockFile->unlock();
424+
if(_engine)
425+
_engine.load()->enterFatalState(QString::fromUtf8(e.what()), QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC, "qtdatasync.setup");
426+
else
427+
qFatal("%s", e.what());
428+
}
429+
}
430+
431+
void EngineThread::stopSelf()
432+
{
433+
stopEngine();
434+
waitAndTerminate(SetupPrivate::currentTimeout());
435+
}

src/datasync/exchangeengine_p.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
#include <QtCore/QObject>
55
#include <QtCore/QAtomicPointer>
6+
#include <QtCore/QThread>
7+
#include <QtCore/QLockFile>
68

79
#include <QtRemoteObjects/QRemoteObjectHost>
810

@@ -45,6 +47,7 @@ class Q_DATASYNC_EXPORT ExchangeEngine : public QObject
4547

4648
explicit ExchangeEngine(const QString &setupName,
4749
Setup::FatalErrorHandler errorHandler);
50+
~ExchangeEngine();
4851

4952
Q_NORETURN void enterFatalState(const QString &error,
5053
const char *file,
@@ -116,6 +119,36 @@ private Q_SLOTS:
116119
void resetProgress(Controller *controller = nullptr);
117120
};
118121

122+
class EngineThread : public QThread
123+
{
124+
Q_OBJECT
125+
126+
public:
127+
EngineThread(QString setupName, ExchangeEngine *engine, QLockFile *lockFile);
128+
~EngineThread() override;
129+
130+
QString name() const;
131+
ExchangeEngine *engine() const;
132+
bool isRunning() const;
133+
134+
bool startEngine();
135+
bool stopEngine();
136+
137+
void waitAndTerminate(unsigned long timeout);
138+
139+
protected:
140+
void run() override;
141+
142+
private Q_SLOTS:
143+
void stopSelf();
144+
145+
private:
146+
const QString _name;
147+
QAtomicPointer<ExchangeEngine> _engine;
148+
QLockFile *_lockFile;
149+
QAtomicInteger<quint16> _running = false;
150+
};
151+
119152
}
120153

121154
#endif // QTDATASYNC_EXCHANGEENGINE_P_H

src/datasync/setup.cpp

Lines changed: 31 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -26,28 +26,13 @@ void Setup::setCleanupTimeout(unsigned long timeout)
2626
void Setup::removeSetup(const QString &name, bool waitForFinished)
2727
{
2828
QMutexLocker _(&SetupPrivate::setupMutex);
29-
if(SetupPrivate::engines.contains(name)) {
30-
auto &info = SetupPrivate::engines[name];
31-
if(info.engine) {
32-
qCDebug(qdssetup) << "Finalizing engine of setup" << name;
33-
QMetaObject::invokeMethod(info.engine, "finalize", Qt::QueuedConnection);
34-
info.engine = nullptr;
35-
} else
36-
qCDebug(qdssetup) << "Engine of setup" << name << "already finalizing";
37-
38-
if(waitForFinished) {
39-
if(!info.thread->wait(SetupPrivate::timeout)) {
40-
qCWarning(qdssetup) << "Workerthread of setup" << name << "did not finish before the timout. Terminating...";
41-
info.thread->terminate();
42-
auto wRes = info.thread->wait(100);
43-
qCDebug(qdssetup) << "Terminate result for setup" << name << ":"
44-
<< wRes;
45-
}
46-
info.thread->deleteLater();
47-
48-
_.unlock(); //unlock first
49-
QCoreApplication::processEvents();//required to perform queued events
50-
}
29+
auto mThread = SetupPrivate::engines.value(name);
30+
_.unlock(); //unlock first
31+
32+
if(mThread) {
33+
mThread->stopEngine();
34+
if(waitForFinished)
35+
mThread->waitAndTerminate(SetupPrivate::timeout);
5136
} else //no there -> remove defaults (either already removed does nothing, or remove passive)
5237
DefaultsPrivate::removeDefaults(name);
5338
}
@@ -443,6 +428,9 @@ Setup &Setup::setAccountTrusted(const QByteArray &importData, const QString &pas
443428

444429
void Setup::create(const QString &name)
445430
{
431+
if(QThread::currentThread() != qApp->thread())
432+
qCWarning(qdssetup) << "Setup::create should only be called from the main thread";
433+
446434
QMutexLocker _(&SetupPrivate::setupMutex);
447435
if(SetupPrivate::engines.contains(name))
448436
throw SetupExistsException(name);
@@ -463,36 +451,22 @@ void Setup::create(const QString &name)
463451
engine->prepareInitialAccount(d->initialImport);
464452

465453
// create and connect the new thread
466-
auto thread = new QThread();
467-
engine->moveToThread(thread);
468-
QObject::connect(thread, &QThread::started,
469-
engine, &ExchangeEngine::initialize);
470-
QObject::connect(thread, &QThread::finished,
471-
engine, &ExchangeEngine::deleteLater);
472-
// unlock as soon as the engine has been destroyed
473-
QObject::connect(engine, &ExchangeEngine::destroyed, qApp, [lockFile, name](){
474-
qCDebug(qdssetup) << "Engine for setup" << name << "destroyed - removing lockfile";
475-
lockFile->unlock();
476-
delete lockFile;
477-
}, Qt::DirectConnection); //direct connection required
454+
auto thread = new EngineThread{name, engine, lockFile};
478455
// once the thread finished, clear the engine from the cache of known ones
479-
QObject::connect(thread, &QThread::finished, thread, [name, thread](){
480-
qCDebug(qdssetup) << "Thread for setup" << name << "stopped - setup completly removed";
456+
QObject::connect(thread, &QThread::finished, [name](){
481457
QMutexLocker _cn(&SetupPrivate::setupMutex);
482458
SetupPrivate::engines.remove(name);
459+
_cn.unlock();
483460
DefaultsPrivate::removeDefaults(name);
484-
thread->deleteLater();
485-
}, Qt::QueuedConnection); //queued connection, sent from within the thread to thread object on current thread
461+
}); //queued connection, sent from within the thread to thread object on current thread
486462

487463
// start the thread and cache engine data
488-
thread->start();
489-
SetupPrivate::engines.insert(name, {thread, engine});
464+
SetupPrivate::engines.insert(name, thread);
465+
thread->startEngine();
490466
}
491467

492468
bool Setup::createPassive(const QString &name, int timeout)
493469
{
494-
QMutexLocker _(&SetupPrivate::setupMutex);
495-
496470
// create storage dir and defaults
497471
auto storageDir = d->createStorageDir(name);
498472
d->createDefaults(name, storageDir, true);
@@ -527,30 +501,27 @@ bool Setup::createPassive(const QString &name, int timeout)
527501

528502
const QString SetupPrivate::DefaultLocalDir = QStringLiteral("./qtdatasync/default");
529503
QMutex SetupPrivate::setupMutex(QMutex::Recursive);
530-
QHash<QString, SetupPrivate::SetupInfo> SetupPrivate::engines;
504+
QHash<QString, QPointer<EngineThread>> SetupPrivate::engines;
531505
unsigned long SetupPrivate::timeout = ULONG_MAX;
532506

533507
void SetupPrivate::cleanupHandler()
534508
{
535509
QMutexLocker _(&setupMutex);
536-
for (auto it = engines.begin(); it != engines.end(); it++) {
537-
if(it->engine) {
538-
qCDebug(qdssetup) << "Finalizing engine of setup" << it.key() << "because of app quit";
539-
QMetaObject::invokeMethod(it->engine, "finalize", Qt::QueuedConnection);
540-
it->engine = nullptr;
541-
}
542-
}
543-
for (auto it = engines.constBegin(); it != engines.constEnd(); it++) {
544-
if(!it->thread->wait(timeout)) {
545-
qCWarning(qdssetup) << "Workerthread of setup" << it.key() << "did not finish before the timout. Terminating...";
546-
it->thread->terminate();
547-
auto wRes = it->thread->wait(100);
548-
qCDebug(qdssetup) << "Terminate result for setup" << it.key() << ":"
549-
<< wRes;
510+
auto threads = engines.values();
511+
engines.clear();
512+
_.unlock();
513+
514+
for(auto &thread : threads) {
515+
if(thread->stopEngine() &&
516+
thread->thread() != QThread::currentThread()) {
517+
qCCritical(qdssetup) << "Stopping datasync thread" << thread->name()
518+
<< "from the main thread even though it was created on a different thread!"
519+
<< "This can lead to synchronization errors. You should always stop all other threads before quitting the application.";
550520
}
551-
it->thread->deleteLater();
552521
}
553-
engines.clear();
522+
for(auto &thread : threads)
523+
thread->waitAndTerminate(timeout);
524+
554525
DefaultsPrivate::clearDefaults();
555526
}
556527

@@ -562,7 +533,7 @@ unsigned long SetupPrivate::currentTimeout()
562533
ExchangeEngine *SetupPrivate::engine(const QString &setupName)
563534
{
564535
QMutexLocker _(&setupMutex);
565-
auto engine = engines.value(setupName).engine;
536+
auto engine = engines.value(setupName)->engine();
566537
if(!engine)
567538
throw SetupDoesNotExistException(setupName);
568539
else
@@ -627,13 +598,6 @@ SetupPrivate::SetupPrivate() :
627598
}
628599
{}
629600

630-
SetupPrivate::SetupInfo::SetupInfo() = default;
631-
632-
SetupPrivate::SetupInfo::SetupInfo(QThread *thread, ExchangeEngine *engine) :
633-
thread{thread},
634-
engine{engine}
635-
{}
636-
637601
// ------------- Exceptions -------------
638602

639603
SetupException::SetupException(const QString &setupName, const QString &message) :

src/datasync/setup_p.h

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <QtCore/QMutex>
55
#include <QtCore/QThread>
6+
#include <QtCore/QPointer>
67

78
#include <QtJsonSerializer/QJsonSerializer>
89

@@ -31,18 +32,10 @@ class Q_DATASYNC_EXPORT SetupPrivate
3132
static QJsonObject parseObj(const QByteArray &data);
3233

3334
private:
34-
struct SetupInfo {
35-
QThread *thread = nullptr;
36-
ExchangeEngine *engine = nullptr;
37-
38-
SetupInfo();
39-
SetupInfo(QThread *thread, ExchangeEngine *engine);
40-
};
41-
4235
static const QString DefaultLocalDir;
4336

4437
static QMutex setupMutex;
45-
static QHash<QString, SetupInfo> engines;
38+
static QHash<QString, QPointer<EngineThread>> engines;
4639
static unsigned long timeout;
4740

4841
QString localDir;
@@ -58,4 +51,6 @@ class Q_DATASYNC_EXPORT SetupPrivate
5851

5952
}
6053

54+
Q_DECLARE_LOGGING_CATEGORY(qdssetup)
55+
6156
#endif // QTDATASYNC_SETUP_P_H

0 commit comments

Comments
 (0)