From ea8d2aafe7e6fac0f92a0d052d73b01a02ee85f3 Mon Sep 17 00:00:00 2001 From: Julien Wadel Date: Wed, 23 Oct 2024 17:29:47 +0200 Subject: [PATCH] Fix deadlocked application on windows at startup (logs,vsync initialization,thread initialization) Fix cleaning memory at exit. --- Linphone/core/App.cpp | 66 +++++++++++++++--------- Linphone/core/App.hpp | 1 + Linphone/core/setting/SettingsCore.cpp | 4 +- Linphone/main.cpp | 40 ++++++++++---- Linphone/model/cli/CliModel.cpp | 3 +- Linphone/model/setting/SettingsModel.cpp | 9 ++-- Linphone/model/setting/SettingsModel.hpp | 2 +- Linphone/tool/thread/Thread.cpp | 5 +- 8 files changed, 86 insertions(+), 44 deletions(-) diff --git a/Linphone/core/App.cpp b/Linphone/core/App.cpp index df66fbc91..c69ef650a 100644 --- a/Linphone/core/App.cpp +++ b/Linphone/core/App.cpp @@ -267,10 +267,6 @@ App::App(int &argc, char *argv[]) QCoreApplication::setApplicationVersion(APPLICATION_SEMVER); // If not OpenGL, createRender is never call. QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); - // Ignore vertical sync. This way, we avoid blinking on resizes(and other refresh like layouts etc.). - auto ignoreVSync = QSurfaceFormat::defaultFormat(); - ignoreVSync.setSwapInterval(0); - QSurfaceFormat::setDefaultFormat(ignoreVSync); setWindowIcon(QIcon(Constants::WindowIconPath)); lInfo() << "Loading Fonts"; QDirIterator it(":/font/", QDirIterator::Subdirectories); @@ -382,9 +378,10 @@ void App::setSelf(QSharedPointer(me)) { new SafeConnection(me, CliModel::getInstance()), &QObject::deleteLater); mCliModelConnection->makeConnectToCore(&App::receivedMessage, [this](int, const QByteArray &byteArray) { QString command(byteArray); - if (command.isEmpty()) - mCliModelConnection->invokeToModel([command]() { CliModel::getInstance()->runProcess(); }); - else { + if (command.isEmpty()) { + lDebug() << log().arg("Check with CliModel for commands"); + mCliModelConnection->invokeToModel([]() { CliModel::getInstance()->runProcess(); }); + } else { qInfo() << QStringLiteral("Received command from other application: `%1`.").arg(command); mCliModelConnection->invokeToModel([command]() { CliModel::getInstance()->executeCommand(command); }); } @@ -398,6 +395,10 @@ App *App::getInstance() { return dynamic_cast(QApplication::instance()); } +QThread *App::getLinphoneThread() { + return App::getInstance()->mLinphoneThread; +} + Notifier *App::getNotifier() const { return mNotifier; } @@ -415,8 +416,10 @@ void App::init() { mParser->process(*this); if (!mLinphoneThread->isRunning()) { - lDebug() << log().arg("Starting Thread"); + lInfo() << log().arg("Starting Thread"); mLinphoneThread->start(); + while (!mLinphoneThread->getThreadId()) // Wait for running thread + processEvents(); } lInfo() << log().arg("Display server : %1").arg(platformName()); @@ -430,10 +433,14 @@ void App::initCore() { QMetaObject::invokeMethod( mLinphoneThread->getThreadId(), [this]() mutable { + lInfo() << log().arg("Starting Core"); CoreModel::getInstance()->start(); auto coreStarted = CoreModel::getInstance()->getCore()->getGlobalState() == linphone::GlobalState::On; + lDebug() << log().arg("Creating SettingsModel"); SettingsModel::create(); + lDebug() << log().arg("Creating SettingsCore"); auto settings = SettingsCore::create(); + lDebug() << log().arg("Creating Ui"); QMetaObject::invokeMethod(App::getInstance()->thread(), [this, settings, coreStarted] { // QML mEngine = new QQmlApplicationEngine(this); @@ -490,6 +497,22 @@ void App::initCore() { Qt::UniqueConnection); setLocale(settings->getConfigLocale()); + if (mSettings) { + mEngine->setObjectOwnership(mSettings.get(), QQmlEngine::CppOwnership); + setAutoStart(mSettings->getAutoStart()); + setQuitOnLastWindowClosed(mSettings->getExitOnClose()); + connect(mSettings.get(), &SettingsCore::exitOnCloseChanged, this, &App::onExitOnCloseChanged, + Qt::UniqueConnection); + setLocale(mSettings->getConfigLocale()); + QObject::connect(mSettings.get(), &SettingsCore::autoStartChanged, [this]() { + mustBeInMainThread(log().arg(Q_FUNC_INFO)); + setAutoStart(mSettings->getAutoStart()); + }); + QObject::connect(mSettings.get(), &SettingsCore::configLocaleChanged, [this]() { + mustBeInMainThread(log().arg(Q_FUNC_INFO)); + if (mSettings) setLocale(mSettings->getConfigLocale()); + }); + } const QUrl url(u"qrc:/qt/qml/Linphone/view/Page/Window/Main/MainWindow.qml"_qs); QObject::connect( mEngine, &QQmlApplicationEngine::objectCreated, this, @@ -510,23 +533,16 @@ void App::initCore() { #endif // ifndef __APPLE__ static bool firstOpen = true; if (!firstOpen || !mParser->isSet("minimized")) { + lDebug() << log().arg("Openning window"); window->show(); - } + } else lInfo() << log().arg("Stay minimized"); firstOpen = false; } }, Qt::QueuedConnection); - QObject::connect(mSettings.get(), &SettingsCore::autoStartChanged, [this]() { - mustBeInMainThread(log().arg(Q_FUNC_INFO)); - setAutoStart(mSettings->getAutoStart()); - }); - QObject::connect(mSettings.get(), &SettingsCore::configLocaleChanged, [this]() { - mustBeInMainThread(log().arg(Q_FUNC_INFO)); - setLocale(mSettings->getConfigLocale()); - }); + mEngine->load(url); }); - // coreModel.reset(); }, Qt::BlockingQueuedConnection); } @@ -628,9 +644,12 @@ void App::clean() { delete mEngine; } mEngine = nullptr; + mSettings = nullptr; // Need it because of SettingsModel singleton for letting thread to remove it. // Wait 500ms to let time for log te be stored. // mNotifier destroyed in mEngine deletion as it is its parent - qApp->processEvents(QEventLoop::AllEvents, 500); + // Hack: exec() must be used to process cleaning QSharedPointers memory. processEvents doesn't work. + QTimer::singleShot(500, [this]() { exit(0); }); + exec(); if (mLinphoneThread) { mLinphoneThread->exit(); mLinphoneThread->wait(); @@ -698,7 +717,8 @@ void App::sendCommand() { receivedMessage(0, i.toLocal8Bit()); } } - } else if (isPrimary()) { // Run waiting process + } else if (isPrimary()) { + lDebug() << "Run waiting process"; receivedMessage(0, ""); } } @@ -825,8 +845,8 @@ QSharedPointer App::getSettings() const { void App::onExitOnCloseChanged() { setSysTrayIcon(); // Restore button depends from this option - setQuitOnLastWindowClosed(mSettings->getExitOnClose()); - setAutoStart(mSettings->getAutoStart()); + if (mSettings) setQuitOnLastWindowClosed(mSettings->getExitOnClose()); + if (mSettings) setAutoStart(mSettings->getAutoStart()); } #ifdef Q_OS_LINUX @@ -952,7 +972,7 @@ void App::setSysTrayIcon() { // trayIcon: Right click actions. QAction *restoreAction = nullptr; - if (!mSettings->getExitOnClose()) { + if (mSettings && !mSettings->getExitOnClose()) { restoreAction = new QAction(root); auto setRestoreActionText = [restoreAction](bool visible) { restoreAction->setText(visible ? tr("Cacher") : tr("Afficher")); diff --git a/Linphone/core/App.hpp b/Linphone/core/App.hpp index 0b96c51a5..d28dc4f1e 100644 --- a/Linphone/core/App.hpp +++ b/Linphone/core/App.hpp @@ -46,6 +46,7 @@ public: ~App(); void setSelf(QSharedPointer(me)); static App *getInstance(); + static QThread *getLinphoneThread(); Notifier *getNotifier() const; // App::postModelAsync() => run lambda in model thread and continue. diff --git a/Linphone/core/setting/SettingsCore.cpp b/Linphone/core/setting/SettingsCore.cpp index 47f44a4df..0e1ae1e9a 100644 --- a/Linphone/core/setting/SettingsCore.cpp +++ b/Linphone/core/setting/SettingsCore.cpp @@ -313,8 +313,8 @@ void SettingsCore::setSelf(QSharedPointer me) { HideSettings) DEFINE_CORE_GETSET_CONNECT(mSettingsModelConnection, SettingsCore, SettingsModel, settingsModel, bool, hideAccountSettings, HideAccountSettings) - DEFINE_CORE_GETSET_CONNECT(mSettingsModelConnection, SettingsCore, SettingsModel, settingsModel, bool, - hideFps, HideFps) + DEFINE_CORE_GETSET_CONNECT(mSettingsModelConnection, SettingsCore, SettingsModel, settingsModel, bool, hideFps, + HideFps) DEFINE_CORE_GETSET_CONNECT(mSettingsModelConnection, SettingsCore, SettingsModel, settingsModel, bool, disableCallRecordings, DisableCallRecordings) DEFINE_CORE_GETSET_CONNECT(mSettingsModelConnection, SettingsCore, SettingsModel, settingsModel, bool, diff --git a/Linphone/main.cpp b/Linphone/main.cpp index 3c00d0670..347add5d5 100644 --- a/Linphone/main.cpp +++ b/Linphone/main.cpp @@ -1,12 +1,13 @@ #include #include -#include -#include -#include - #include "core/App.hpp" - +#include "core/logger/QtLogger.hpp" +#include +#include +#include +#include +#include #ifdef QT_QML_DEBUG #include #endif @@ -36,8 +37,12 @@ void cleanStream() { } #endif } - +void fallbackLog(QtMsgType type, const QMessageLogContext &context, const QString &msg) { + QString message = QtLogger::formatLog(context.file, context.line, msg); + std::cout << message.toStdString() << std::endl; +} int main(int argc, char *argv[]) { + qInstallMessageHandler(fallbackLog); // Use for messages prior QCoreApplication generation. /* #if defined _WIN32 // log in console only if launched from console @@ -48,37 +53,52 @@ int main(int argc, char *argv[]) { #endif */ // Useful to share camera on Fullscreen (other context) or multiscreens + qDebug() << "[Main] Setting ShareOpenGLContexts"; QApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + qDebug() << "[Main] Disabling VSync"; + // Ignore vertical sync. This way, we avoid blinking on resizes(and other refresh like layouts etc.). + auto ignoreVSync = QSurfaceFormat::defaultFormat(); + ignoreVSync.setSwapInterval(0); + QSurfaceFormat::setDefaultFormat(ignoreVSync); // Disable QML cache. Avoid malformed cache. + qDebug() << "[Main] Disabling QML disk cache"; qputenv("QML_DISABLE_DISK_CACHE", "true"); + qDebug() << "[Main] Setting application to UTF8"; setlocale(LC_CTYPE, ".UTF8"); - + qDebug() << "[Main] Creating application"; auto app = QSharedPointer::create(argc, argv); - #ifdef ACCESSBILITY_WORKAROUND QAccessible::installUpdateHandler(DummyUpdateHandler); QAccessible::installRootObjectHandler(DummyRootObjectHandler); #endif if (app->isSecondary()) { + qDebug() << "[Main] Sending command from secondary application"; app->sendCommand(); - qInfo() << QStringLiteral("Running secondary app success. Kill it now."); + qInfo() << QStringLiteral("[Main] Running secondary app success. Kill it now."); app->clean(); cleanStream(); return EXIT_SUCCESS; } else { + qDebug() << "[Main] Initializing core for primary application"; app->initCore(); + qDebug() << "[Main] Preparing application's connections"; app->setSelf(app); } int result = 0; do { + qDebug() << "[Main] Sending command from primary application"; app->sendCommand(); + qInfo() << "[Main] Running application"; result = app->exec(); } while (result == (int)App::StatusCode::gRestartCode); - qWarning() << "[Main] Exiting app with the code : " << result; + QString message = "[Main] Exiting app with the code : " + QString::number(result); + if (result) qInfo() << message; + else qWarning() << message; app->clean(); app = nullptr; + cleanStream(); return result; } diff --git a/Linphone/model/cli/CliModel.cpp b/Linphone/model/cli/CliModel.cpp index 1f7844aff..2554b0da4 100644 --- a/Linphone/model/cli/CliModel.cpp +++ b/Linphone/model/cli/CliModel.cpp @@ -395,9 +395,10 @@ void CliModel::addProcess(ProcessCommand process) { void CliModel::runProcess() { if (mQueue.size() > 0) { + lInfo() << log().arg("Processing command from queue"); mQueue.first().run(); mQueue.pop_front(); - } + } else lInfo() << log().arg("Queue is empty. Nothing to do."); } void CliModel::resetProcesses() { diff --git a/Linphone/model/setting/SettingsModel.cpp b/Linphone/model/setting/SettingsModel.cpp index 1bdf05d42..5a8ba9a76 100644 --- a/Linphone/model/setting/SettingsModel.cpp +++ b/Linphone/model/setting/SettingsModel.cpp @@ -31,13 +31,13 @@ DEFINE_ABSTRACT_OBJECT(SettingsModel) using namespace std; const std::string SettingsModel::UiSection("ui"); -std::shared_ptr SettingsModel::gCoreModel; +std::shared_ptr SettingsModel::gSettingsModel; SettingsModel::SettingsModel() { mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); connect(CoreModel::getInstance()->thread(), &QThread::finished, this, [this]() { // Model thread - gCoreModel = nullptr; + gSettingsModel = nullptr; }); auto core = CoreModel::getInstance()->getCore(); mConfig = core->getConfig(); @@ -69,13 +69,14 @@ SettingsModel::~SettingsModel() { } shared_ptr SettingsModel::create() { + // auto model = Utils::makeQObject_ptr(); auto model = make_shared(); - gCoreModel = model; + gSettingsModel = model; return model; } shared_ptr SettingsModel::getInstance() { - return gCoreModel; + return gSettingsModel; } bool SettingsModel::isReadOnly(const std::string §ion, const std::string &name) const { diff --git a/Linphone/model/setting/SettingsModel.hpp b/Linphone/model/setting/SettingsModel.hpp index 19d1028a8..cd77a4cd8 100644 --- a/Linphone/model/setting/SettingsModel.hpp +++ b/Linphone/model/setting/SettingsModel.hpp @@ -196,7 +196,7 @@ private: MediastreamerUtils::SimpleCaptureGraph *mSimpleCaptureGraph = nullptr; int mCaptureGraphListenerCount = 0; - static std::shared_ptr gCoreModel; + static std::shared_ptr gSettingsModel; DECLARE_ABSTRACT_OBJECT }; diff --git a/Linphone/tool/thread/Thread.cpp b/Linphone/tool/thread/Thread.cpp index 631708a26..c02634a6e 100644 --- a/Linphone/tool/thread/Thread.cpp +++ b/Linphone/tool/thread/Thread.cpp @@ -20,16 +20,15 @@ #include "Thread.hpp" #include "core/App.hpp" -#include "model/core/CoreModel.hpp" #include Thread::Thread(QObject *parent) : QThread(parent) { } void Thread::run() { + mThreadId = new QObject(); setlocale(LC_CTYPE, ".UTF8"); int toExit = false; - mThreadId = new QObject(); while (!toExit) { int result = exec(); if (result <= 0) toExit = true; @@ -44,7 +43,7 @@ QObject *Thread::getThreadId() { } bool Thread::isInLinphoneThread() { - return CoreModel::getInstance() && QThread::currentThread() == CoreModel::getInstance()->thread(); + return QThread::currentThread() == App::getLinphoneThread(); } bool Thread::mustBeInLinphoneThread(const QString &context) {