Skip to content

Commit

Permalink
Core: Don't freeze application if lock file already exists
Browse files Browse the repository at this point in the history
  • Loading branch information
wwmayer committed Nov 1, 2024
1 parent b7306d2 commit 46bc0aa
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 101 deletions.
84 changes: 33 additions & 51 deletions src/Gui/Application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@
#include "PreCompiled.h"

#ifndef _PreComp_
#include <boost/interprocess/sync/file_lock.hpp>
#include <Inventor/errors/SoDebugError.h>
#include <Inventor/errors/SoError.h>
#include <QCloseEvent>
#include <QDir>
#include <QFileInfo>
#include <QLocale>
#include <QLockFile>
#include <QMessageBox>
#include <QMessageLogContext>
#include <QRegularExpression>
Expand Down Expand Up @@ -2172,46 +2172,35 @@ void setAppNameAndIcon()

void tryRunEventLoop(GUISingleApplication& mainApp)
{
std::stringstream s;
s << App::Application::getUserCachePath() << App::Application::getExecutableName() << "_"
<< QCoreApplication::applicationPid() << ".lock";
// open a lock file with the PID
Base::FileInfo fi(s.str());
Base::ofstream lock(fi);
std::stringstream out;
out << App::Application::getUserCachePath()
<< App::Application::getExecutableName()
<< "_"
<< QCoreApplication::applicationPid()
<< ".lock";

// In case the file_lock cannot be created start FreeCAD without IPC support.
#if !defined(FC_OS_WIN32) || (BOOST_VERSION < 107600)
std::string filename = s.str();
#else
std::wstring filename = fi.toStdWString();
#endif
std::unique_ptr<boost::interprocess::file_lock> flock;
try {
flock = std::make_unique<boost::interprocess::file_lock>(filename.c_str());
flock->lock();
}
catch (const boost::interprocess::interprocess_exception& e) {
QString msg = QString::fromLocal8Bit(e.what());
Base::Console().Warning("Failed to create a file lock for the IPC: %s\n",
msg.toUtf8().constData());
}
// open a lock file with the PID
QString filename = QString::fromStdString(out.str());
QLockFile flock(filename);
if (flock.tryLock()) {
Base::Console().Log("Init: Executing event loop...\n");
QApplication::exec();

Base::Console().Log("Init: Executing event loop...\n");
QApplication::exec();
// Qt can't handle exceptions thrown from event handlers, so we need
// to manually rethrow SystemExitExceptions.
if (mainApp.caughtException) {
throw Base::SystemExitException(*mainApp.caughtException.get());
}

// Qt can't handle exceptions thrown from event handlers, so we need
// to manually rethrow SystemExitExceptions.
if (mainApp.caughtException) {
throw Base::SystemExitException(*mainApp.caughtException.get());
// close the lock file, in case of a crash we can see the existing lock file
// on the next restart and try to repair the documents, if needed.
flock.unlock();
}
else {
Base::Console().Warning("Failed to create a file lock for the IPC.\n"
"The application will be terminated\n");

// close the lock file, in case of a crash we can see the existing lock file
// on the next restart and try to repair the documents, if needed.
if (flock) {
flock->unlock();
}
lock.close();
fi.deleteFile();
}

void runEventLoop(GUISingleApplication& mainApp)
Expand Down Expand Up @@ -2475,24 +2464,17 @@ void Application::checkForDeprecatedSettings()

void Application::checkForPreviousCrashes()
{
try {
Gui::Dialog::DocumentRecoveryFinder finder;
if (!finder.checkForPreviousCrashes()) {

// If the recovery dialog wasn't shown check the cache size periodically
Gui::Dialog::ApplicationCache cache;
cache.applyUserSettings();
if (cache.periodicCheckOfSize()) {
qint64 total = cache.size();
cache.performAction(total);
}
Gui::Dialog::DocumentRecoveryFinder finder;
if (!finder.checkForPreviousCrashes()) {

// If the recovery dialog wasn't shown check the cache size periodically
Gui::Dialog::ApplicationCache cache;
cache.applyUserSettings();
if (cache.periodicCheckOfSize()) {
qint64 total = cache.size();
cache.performAction(total);
}
}
catch (const boost::interprocess::interprocess_exception& e) {
QString msg = QString::fromLocal8Bit(e.what());
Base::Console().Warning("Failed check for previous crashes because of IPC error: %s\n",
msg.toUtf8().constData());
}
}

App::Document* Application::reopen(App::Document* doc)
Expand Down
89 changes: 39 additions & 50 deletions src/Gui/DocumentRecovery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
#include "PreCompiled.h"

#ifndef _PreComp_
# include <boost/interprocess/sync/file_lock.hpp>
# include <QApplication>
# include <QCloseEvent>
# include <QDateTime>
Expand All @@ -37,6 +36,7 @@
# include <QFileInfo>
# include <QHeaderView>
# include <QList>
# include <QLockFile>
# include <QMap>
# include <QMenu>
# include <QMessageBox>
Expand Down Expand Up @@ -584,45 +584,35 @@ bool DocumentRecoveryFinder::checkForPreviousCrashes()

void DocumentRecoveryFinder::checkDocumentDirs(QDir& tmp, const QList<QFileInfo>& dirs, const QString& fn)
{
if (dirs.isEmpty()) {
// delete the lock file immediately if no transient directories are related
tmp.remove(fn);
}
else {
int countDeletedDocs = 0;
QString recovery_files = QString::fromLatin1("fc_recovery_files");
for (QList<QFileInfo>::const_iterator it = dirs.cbegin(); it != dirs.cend(); ++it) {
QDir doc_dir(it->absoluteFilePath());
doc_dir.setFilter(QDir::NoDotAndDotDot|QDir::AllEntries);
uint entries = doc_dir.entryList().count();
if (entries == 0) {
// in this case we can delete the transient directory because
// we cannot do anything
if (tmp.rmdir(it->filePath()))
countDeletedDocs++;
}
// search for the existence of a recovery file
else if (doc_dir.exists(QLatin1String("fc_recovery_file.xml"))) {
// store the transient directory in case it's not empty
restoreDocFiles << *it;
}
// search for the 'fc_recovery_files' sub-directory and check that it's the only entry
else if (entries == 1 && doc_dir.exists(recovery_files)) {
// if the sub-directory is empty delete the transient directory
QDir rec_dir(doc_dir.absoluteFilePath(recovery_files));
rec_dir.setFilter(QDir::NoDotAndDotDot|QDir::AllEntries);
if (rec_dir.entryList().isEmpty()) {
doc_dir.rmdir(recovery_files);
if (tmp.rmdir(it->filePath()))
countDeletedDocs++;
}
}
Q_UNUSED(fn)

QString recovery_files = QString::fromLatin1("fc_recovery_files");
for (QList<QFileInfo>::const_iterator it = dirs.cbegin(); it != dirs.cend(); ++it) {
Base::Console().Warning("File: %s\n", it->absoluteFilePath().toUtf8().constData());
QDir doc_dir(it->absoluteFilePath());
doc_dir.setFilter(QDir::NoDotAndDotDot|QDir::AllEntries);
uint entries = doc_dir.entryList().count();
if (entries == 0) {
// in this case we can delete the transient directory because
// we cannot do anything
bool ok = tmp.rmdir(it->filePath());
Base::Console().Warning("Delete %s\n", (ok ? "OK" : "Failed"));
}

// all directories corresponding to the lock file have been deleted
// so delete the lock file, too
if (countDeletedDocs == dirs.size()) {
tmp.remove(fn);
// search for the existence of a recovery file
else if (doc_dir.exists(QLatin1String("fc_recovery_file.xml"))) {
// store the transient directory in case it's not empty
restoreDocFiles << *it;
}
// search for the 'fc_recovery_files' sub-directory and check that it's the only entry
else if (entries == 1 && doc_dir.exists(recovery_files)) {
// if the sub-directory is empty delete the transient directory
QDir rec_dir(doc_dir.absoluteFilePath(recovery_files));
rec_dir.setFilter(QDir::NoDotAndDotDot|QDir::AllEntries);
if (rec_dir.entryList().isEmpty()) {
doc_dir.rmdir(recovery_files);
bool ok = tmp.rmdir(it->filePath());
Base::Console().Warning("Delete %s\n", (ok ? "OK" : "Failed"));
}
}
}
}
Expand Down Expand Up @@ -651,21 +641,17 @@ void DocumentRecoveryHandler::checkForPreviousCrashes(const std::function<void(Q

QString exeName = QString::fromStdString(App::Application::getExecutableName());
QList<QFileInfo> locks = tmp.entryInfoList();
for (QList<QFileInfo>::iterator it = locks.begin(); it != locks.end(); ++it) {
QString bn = it->baseName();
for (const QFileInfo& it : locks) {
QString bn = it.baseName();
// ignore the lock file for this instance
QString pid = QString::number(QCoreApplication::applicationPid());
if (bn.startsWith(exeName) && bn.indexOf(pid) < 0) {
QString fn = it->absoluteFilePath();
QString fn = it.absoluteFilePath();

#if !defined(FC_OS_WIN32) || (BOOST_VERSION < 107600)
boost::interprocess::file_lock flock(fn.toUtf8());
#else
boost::interprocess::file_lock flock(fn.toStdWString().c_str());
#endif
if (flock.try_lock()) {
QLockFile flock(fn);
if (flock.tryLock()) {
// OK, this file is a leftover from a previous crash
QString crashed_pid = bn.mid(exeName.length()+1);
QString crashed_pid = bn.mid(exeName.length() + 1);
// search for transient directories with this PID
QString filter;
QTextStream str(&filter);
Expand All @@ -674,7 +660,10 @@ void DocumentRecoveryHandler::checkForPreviousCrashes(const std::function<void(Q
tmp.setFilter(QDir::Dirs);
QList<QFileInfo> dirs = tmp.entryInfoList();

callableFunc(tmp, dirs, it->fileName());
callableFunc(tmp, dirs, it.fileName());
}
else {
Base::Console().Message("Cannot lock %s\n", fn.toUtf8().constData());
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Gui/QtAll.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include <QFile>
#include <QLibraryInfo>
#include <QLocale>
#include <QLockFile>
#include <QMutex>
#include <qmath.h>
#include <QMessageLogContext>
Expand Down

0 comments on commit 46bc0aa

Please sign in to comment.