Use Boost.Process for launcher to cope with further sub processes

* Use a process group / job object via Boost.Process to be able to
  terminate sub processes as well
* Do not try to stop the process gracefully under Windows by posting
  WM_CLOSE because this has no effect on Syncthing anyways
* See https://github.com/Martchus/syncthingtray/issues/94
This commit is contained in:
Martchus 2021-06-09 20:59:09 +02:00
parent 7f55afc51c
commit 9ce9b11ba4
13 changed files with 553 additions and 48 deletions

View File

@ -48,6 +48,34 @@ use_cpp_utilities(VISIBILITY PUBLIC)
find_package(qtutilities${CONFIGURATION_PACKAGE_SUFFIX_QTUTILITIES} 6.3.0 REQUIRED)
use_qt_utilities(ONLY_HEADERS VISIBILITY PUBLIC)
# find boost libraries
option(USE_BOOST_PROCESS "enables Boost.Process for improved behavior of the launcher" ON)
if (USE_BOOST_PROCESS)
option(BOOST_STATIC_LINKAGE "${STATIC_LINKAGE}" "link statically against Boost (instead of dynamically)")
set(Boost_USE_MULTITHREADED ON)
if (BOOST_STATIC_LINKAGE)
set(Boost_USE_STATIC_LIBS ON)
endif ()
# add Boost::boost target which represents include directory for header-only deps add Boost::filesystem when building for
# Windows as it is needed by Boost.Process there
set(BOOST_ARGS REQUIRED)
if (WIN32)
list(APPEND BOOST_ARGS COMPONENTS filesystem)
endif ()
use_package(TARGET_NAME Boost::boost PACKAGE_NAME Boost PACKAGE_ARGS "${BOOST_ARGS}")
if (WIN32)
use_package(TARGET_NAME Boost::filesystem PACKAGE_NAME Boost PACKAGE_ARGS "${BOOST_ARGS}")
endif ()
list(APPEND META_PUBLIC_COMPILE_DEFINITIONS ${META_PROJECT_VARNAME_UPPER}_BOOST_PROCESS)
if (MINGW)
# workaround https://github.com/boostorg/process/issues/96
set_property(
SOURCE syncthingprocess.cpp
APPEND
PROPERTY COMPILE_DEFINITIONS BOOST_USE_WINDOWS_H WIN32_LEAN_AND_MEAN)
endif ()
endif ()
# link also explicitely against the following Qt modules
list(APPEND ADDITIONAL_QT_MODULES Network)
set(META_PUBLIC_QT_MODULES Core ${ADDITIONAL_QT_MODULES})

View File

@ -42,7 +42,7 @@ SyncthingNotifier::SyncthingNotifier(const SyncthingConnection &connection, QObj
connect(&connection, &SyncthingConnection::newDevAvailable, this, &SyncthingNotifier::handleNewDevEvent);
connect(&connection, &SyncthingConnection::newDirAvailable, this, &SyncthingNotifier::handleNewDirEvent);
if (m_process) {
connect(m_process, &QProcess::errorOccurred, this, &SyncthingNotifier::handleSyncthingProcessError);
connect(m_process, &SyncthingProcess::errorOccurred, this, &SyncthingNotifier::handleSyncthingProcessError);
}
}
@ -101,16 +101,17 @@ void SyncthingNotifier::handleSyncthingProcessError(QProcess::ProcessError proce
return;
}
const auto error = m_process->errorString();
switch (processError) {
case QProcess::FailedToStart:
emit syncthingProcessError(
tr("Failed to start Syncthing"), tr("Maybe the configured binary path is wrong or the binary is not marked as executable."));
emit syncthingProcessError(tr("Failed to start Syncthing"),
error.isEmpty() ? tr("Maybe the configured binary path is wrong or the binary is not marked as executable.") : error);
break;
case QProcess::Crashed:
emit syncthingProcessError(tr("Syncthing crashed with exit code %1").arg(m_process->exitCode()), QString());
emit syncthingProcessError(tr("Syncthing crashed"), error);
break;
default:
emit syncthingProcessError(tr("Syncthing launcher error occurred"), m_process->errorString());
emit syncthingProcessError(tr("Syncthing launcher error occurred"), error);
}
}

View File

@ -2,10 +2,69 @@
#include <QTimer>
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
#include <c++utilities/io/ansiescapecodes.h>
#include <boost/asio/executor_work_guard.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/process/async.hpp>
#include <boost/process/async_pipe.hpp>
#include <boost/process/child.hpp>
#include <boost/process/extend.hpp>
#include <boost/process/group.hpp>
#include <boost/process/io.hpp>
#include <atomic>
#include <chrono>
#include <csignal>
#include <iostream>
#include <limits>
#include <mutex>
#include <system_error>
#include <thread>
#endif
using namespace CppUtilities;
namespace Data {
/// \cond
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
struct SyncthingProcessInternalData : std::enable_shared_from_this<SyncthingProcessInternalData> {
static constexpr std::size_t bufferCapacity = 0x1000;
static_assert(SyncthingProcessInternalData::bufferCapacity <= std::numeric_limits<qint64>::max());
explicit SyncthingProcessInternalData(boost::asio::io_context &ioc);
struct Lock {
explicit Lock(const std::weak_ptr<SyncthingProcessInternalData> &weak);
operator bool() const;
std::shared_ptr<SyncthingProcessInternalData> process;
std::unique_lock<std::mutex> lock;
};
std::mutex mutex;
QString program;
QStringList arguments;
boost::process::group group;
boost::process::child child;
boost::process::async_pipe pipe;
char buffer[bufferCapacity];
std::atomic_size_t bytesBuffered = 0;
std::size_t bytesRead = 0;
QProcess::ProcessState state = QProcess::NotRunning;
};
struct SyncthingProcessIOHandler {
explicit SyncthingProcessIOHandler();
~SyncthingProcessIOHandler();
boost::asio::io_context ioc;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> guard;
std::thread t;
};
#endif
/// \endcond
SyncthingProcess *SyncthingProcess::s_mainInstance = nullptr;
/*!
@ -20,18 +79,35 @@ SyncthingProcess *SyncthingProcess::s_mainInstance = nullptr;
* \brief Constructs a new Syncthing process.
*/
SyncthingProcess::SyncthingProcess(QObject *parent)
: QProcess(parent)
: SyncthingProcessBase(parent)
, m_manuallyStopped(true)
{
m_killTimer.setInterval(3000);
m_killTimer.setSingleShot(true);
#ifndef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
setProcessChannelMode(QProcess::MergedChannels);
#endif
connect(this, &SyncthingProcess::started, this, &SyncthingProcess::handleStarted);
connect(this, static_cast<void (SyncthingProcess::*)(int exitCode, QProcess::ExitStatus exitStatus)>(&SyncthingProcess::finished), this,
&SyncthingProcess::handleFinished);
connect(&m_killTimer, &QTimer::timeout, this, &SyncthingProcess::confirmKill);
}
/*!
* \brief Destroys the process.
*/
SyncthingProcess::~SyncthingProcess()
{
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
// block until all callbacks have been processed
if (!m_process) {
return;
}
const auto lock = std::lock_guard<std::mutex>(m_process->mutex);
m_process.reset();
#endif
}
/*!
* \brief Splits the given arguments similar to how a shell would split it. So whitespaces are considered seperators unless quotes are used.
*/
@ -129,7 +205,7 @@ void SyncthingProcess::startSyncthing(const QString &program, const QStringList
}
m_manuallyStopped = false;
m_killTimer.stop();
start(program, arguments, QProcess::ReadOnly);
start(program, arguments, QIODevice::ReadOnly);
}
/*!
@ -137,9 +213,6 @@ void SyncthingProcess::startSyncthing(const QString &program, const QStringList
*/
void SyncthingProcess::stopSyncthing()
{
if (!isRunning()) {
return;
}
m_manuallyStopped = true;
m_killTimer.start();
terminate();
@ -150,9 +223,6 @@ void SyncthingProcess::stopSyncthing()
*/
void SyncthingProcess::killSyncthing()
{
if (!isRunning()) {
return;
}
m_manuallyStopped = true;
m_killTimer.stop();
kill();
@ -183,4 +253,342 @@ void SyncthingProcess::killToRestart()
}
}
/// \cond
/// \remarks The functions below are for using Boost.Process (instead of QProcess) to be able to
/// terminate the process better by using a group.
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
SyncthingProcessIOHandler::SyncthingProcessIOHandler()
: ioc()
, guard(boost::asio::make_work_guard(ioc))
, t([this] { ioc.run(); })
{
}
SyncthingProcessIOHandler::~SyncthingProcessIOHandler()
{
ioc.stop();
guard.reset();
t.join();
}
Data::SyncthingProcessInternalData::SyncthingProcessInternalData(boost::asio::io_context &ioc)
: pipe(ioc)
{
}
inline Data::SyncthingProcessInternalData::Lock::Lock(const std::weak_ptr<SyncthingProcessInternalData> &weak)
: process(weak.lock())
, lock(process ? decltype(lock)(process->mutex, std::try_to_lock) : decltype(lock)())
{
}
inline Data::SyncthingProcessInternalData::Lock::operator bool() const
{
return process && lock;
}
void SyncthingProcess::handleError(QProcess::ProcessError error, const QString &errorMessage, bool closed)
{
setErrorString(errorMessage);
errorOccurred(error);
if (closed) {
setOpenMode(QIODevice::NotOpen);
}
}
QProcess::ProcessState SyncthingProcess::state() const
{
return !m_process ? QProcess::NotRunning : m_process->state;
}
void SyncthingProcess::start(const QString &program, const QStringList &arguments, QIODevice::OpenMode openMode)
{
// ensure the dev is only opened in read-only mode because writing is not implemented here
if (openMode != QIODevice::ReadOnly) {
setErrorString(QStringLiteral("only read-only openmode supported"));
std::cerr << EscapeCodes::Phrases::Error << "Unable to launch process: only read-only openmode supported" << EscapeCodes::Phrases::End;
emit errorOccurred(QProcess::FailedToStart);
return;
}
// handle current/previous process
if (m_process) {
const auto lock = std::lock_guard<std::mutex>(m_process->mutex);
if (m_process->state != QProcess::NotRunning) {
setErrorString(QStringLiteral("process is still running"));
std::cerr << EscapeCodes::Phrases::Error << "Unable to launch process: previous process still running" << EscapeCodes::Phrases::End;
emit errorOccurred(QProcess::FailedToStart);
return;
}
m_process.reset();
}
// setup new handler/process
if (!m_handler) {
m_handler = std::make_unique<SyncthingProcessIOHandler>();
}
m_process = std::make_shared<SyncthingProcessInternalData>(m_handler->ioc);
emit stateChanged(m_process->state = QProcess::Starting);
// convert args
auto args = std::vector<std::string>();
args.reserve(1 + static_cast<std::size_t>(arguments.size()));
args.emplace_back(program.toStdString());
for (const auto &arg : arguments) {
args.emplace_back(arg.toStdString());
}
m_process->program = program;
m_process->arguments = arguments;
// start the process within a new process group (or job object under Windows)
try {
m_process->child = boost::process::child(
m_handler->ioc, m_process->group, args, (boost::process::std_out & boost::process::std_err) > m_process->pipe,
boost::process::extend::on_success =
[this, maybeProcess = m_process->weak_from_this()](auto &executor) {
std::cerr << EscapeCodes::Phrases::Info << "Launched process, PID: "
<< executor
#ifdef PLATFORM_WINDOWS
.proc_info.dwProcessId
#else
.pid
#endif
<< EscapeCodes::Phrases::End;
if (const auto lock = SyncthingProcessInternalData::Lock(maybeProcess)) {
emit stateChanged(m_process->state = QProcess::Running);
emit started();
}
},
boost::process::on_exit =
[this, maybeProcess = m_process->weak_from_this()](int rc, const std::error_code &ec) {
const auto lock = SyncthingProcessInternalData::Lock(maybeProcess);
if (!lock) {
return;
}
handleLeftoverProcesses();
emit stateChanged(m_process->state = QProcess::NotRunning);
emit finished(rc, rc != 0 ? QProcess::CrashExit : QProcess::NormalExit);
if (ec) {
const auto msg = ec.message();
std::cerr << EscapeCodes::Phrases::Error << "Launched process " << m_process->child.native_handle()
<< " exited with error: " << msg << EscapeCodes::Phrases::End;
QMetaObject::invokeMethod(this, "handleError", Qt::QueuedConnection, Q_ARG(QProcess::ProcessError, QProcess::Crashed),
Q_ARG(QString, QString::fromStdString(msg)), Q_ARG(bool, false));
}
},
boost::process::extend::on_error =
[this, maybeProcess = m_process->weak_from_this()](auto &, const std::error_code &ec) {
const auto lock = SyncthingProcessInternalData::Lock(maybeProcess);
if (!lock) {
return;
}
handleLeftoverProcesses();
const auto started = m_process->state == QProcess::Running;
if (m_process->state != QProcess::NotRunning) {
emit stateChanged(m_process->state = QProcess::NotRunning);
}
if (started) {
emit finished(0, QProcess::CrashExit);
}
const auto error = ec == std::errc::timed_out || ec == std::errc::stream_timeout ? QProcess::Timedout : QProcess::Crashed;
const auto msg = ec.message();
std::cerr << EscapeCodes::Phrases::Error << "Unable to launch process: " << msg << EscapeCodes::Phrases::End;
QMetaObject::invokeMethod(this, "handleError", Qt::QueuedConnection, Q_ARG(QProcess::ProcessError, error),
Q_ARG(QString, QString::fromStdString(msg)), Q_ARG(bool, false));
});
} catch (const boost::process::process_error &e) {
std::cerr << EscapeCodes::Phrases::Error << "Unable to launch process: " << e.what() << EscapeCodes::Phrases::End;
emit stateChanged(m_process->state = QProcess::NotRunning);
handleError(QProcess::FailedToStart, QString::fromUtf8(e.what()), false);
return;
}
// start reading the process' output
open(QIODevice::ReadOnly);
bufferOutput();
}
void SyncthingProcess::terminate()
{
if (!m_process) {
return;
}
#ifdef PLATFORM_UNIX
auto lock = std::unique_lock<std::mutex>(m_process->mutex);
if (!m_process->group.valid()) {
return;
}
const auto groupId = m_process->group.native_handle();
lock.unlock();
if (::killpg(groupId, SIGTERM) == -1) {
if (const auto ec = boost::process::detail::get_last_error(); ec != std::errc::no_such_process) {
const auto msg = ec.message();
std::cerr << EscapeCodes::Phrases::Error << "Unable to kill process group " << groupId << ": " << msg << EscapeCodes::Phrases::End;
setErrorString(QString::fromStdString(msg));
errorOccurred(QProcess::UnknownError);
}
}
#else
// there seems no way to stop the process group gracefully under Windows so just kill it
// note: Posting a WM_CLOSE message like QProcess would attempt doesn't work for Syncthing.
kill();
#endif
}
void SyncthingProcess::kill()
{
if (!m_process) {
return;
}
auto ec = std::error_code();
auto lock = std::unique_lock<std::mutex>(m_process->mutex);
if (!m_process->group.valid()) {
return;
}
const auto groupId = m_process->group.native_handle();
m_process->group.terminate(ec);
lock.unlock();
if (ec && ec != std::errc::no_such_process) {
const auto msg = ec.message();
std::cerr << EscapeCodes::Phrases::Error << "Unable to kill process group " << groupId << ": " << msg << EscapeCodes::Phrases::End;
setErrorString(QString::fromStdString(msg));
errorOccurred(QProcess::UnknownError);
}
// note: No need to emit finished() signal here, the on_exit handler will fire
// also in case of a forceful termination.
}
void SyncthingProcess::bufferOutput()
{
m_process->pipe.async_read_some(boost::asio::buffer(m_process->buffer, m_process->bufferCapacity),
[this, maybeProcess = m_process->weak_from_this()](const boost::system::error_code &ec, auto bytesRead) {
const auto lock = SyncthingProcessInternalData::Lock(maybeProcess);
if (!lock) {
return;
}
m_process->bytesBuffered = bytesRead;
if (ec == boost::asio::error::eof
#ifdef PLATFORM_WINDOWS // looks like we're getting broken pipe (and not just eof) under Windows when stopping the process
|| ec == boost::asio::error::broken_pipe
#endif
) {
m_process->pipe.async_close();
setOpenMode(QIODevice::NotOpen);
} else if (ec) {
const auto msg = ec.message();
std::cerr << EscapeCodes::Phrases::Error << "Unable to read output of process " << m_process->child.native_handle() << ": " << msg
<< EscapeCodes::Phrases::End;
QMetaObject::invokeMethod(this, "handleError", Qt::QueuedConnection, Q_ARG(QProcess::ProcessError, QProcess::ReadError),
Q_ARG(QString, QString::fromStdString(msg)), Q_ARG(bool, true));
}
if (!ec || bytesRead) {
emit readyRead();
}
});
}
void SyncthingProcess::handleLeftoverProcesses()
{
if (!m_process->group.valid()) {
return;
}
auto ec = std::error_code();
m_process->group.terminate(ec);
if (ec && ec != std::errc::no_such_process) {
std::cerr << EscapeCodes::Phrases::Error << "Unable to kill leftover processes in group " << m_process->group.native_handle() << ": "
<< ec.message() << EscapeCodes::Phrases::End;
}
m_process->group.wait(ec);
if (ec && ec != std::errc::no_such_process) {
std::cerr << EscapeCodes::Phrases::Error << "Unable to wait for leftover processes in group " << m_process->group.native_handle() << ": "
<< ec.message() << EscapeCodes::Phrases::End;
}
}
qint64 SyncthingProcess::bytesAvailable() const
{
return (m_process ? static_cast<qint64>(m_process->bytesBuffered) : 0) + QIODevice::bytesAvailable();
}
void SyncthingProcess::close()
{
emit aboutToClose();
if (m_process) {
const auto lock = std::lock_guard<std::mutex>(m_process->mutex);
m_process->pipe.async_close();
kill();
}
setOpenMode(QIODevice::NotOpen);
}
int SyncthingProcess::exitCode() const
{
return m_process ? m_process->child.exit_code() : 0;
}
bool SyncthingProcess::waitForFinished(int msecs)
{
if (!m_process) {
return false;
}
auto ec = std::error_code();
if (msecs < 0) {
m_process->group.wait(ec);
} else {
m_process->group.wait_for(std::chrono::milliseconds(msecs), ec);
}
return !ec || ec == std::errc::no_such_process || ec == std::errc::no_child_process;
}
qint64 SyncthingProcess::processId() const
{
return m_process ? m_process->child.id() : -1;
}
QString SyncthingProcess::program() const
{
return m_process ? m_process->program : QString();
}
QStringList SyncthingProcess::arguments() const
{
return m_process ? m_process->arguments : QStringList();
}
qint64 SyncthingProcess::readData(char *data, qint64 maxSize)
{
if (!m_process) {
return -1;
}
if (maxSize < 1) {
return 0;
}
if (!m_process->bytesBuffered) {
bufferOutput();
return 0;
}
const auto bytesAvailable = m_process->bytesBuffered - m_process->bytesRead;
if (bytesAvailable > static_cast<std::size_t>(maxSize)) {
std::memcpy(data, m_process->buffer + m_process->bytesRead, static_cast<std::size_t>(maxSize));
m_process->bytesRead += static_cast<std::size_t>(maxSize);
return maxSize;
} else {
std::memcpy(data, m_process->buffer + m_process->bytesRead, bytesAvailable);
m_process->bytesBuffered = 0;
m_process->bytesRead = 0;
bufferOutput();
return static_cast<qint64>(bytesAvailable);
}
}
qint64 SyncthingProcess::writeData(const char *data, qint64 len)
{
Q_UNUSED(data)
Q_UNUSED(len)
return -1;
}
#endif
/// \endcond
} // namespace Data

View File

@ -9,9 +9,21 @@
#include <QStringList>
#include <QTimer>
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
#include <memory>
#endif
namespace Data {
class LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingProcess : public QProcess {
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
struct SyncthingProcessInternalData;
struct SyncthingProcessIOHandler;
using SyncthingProcessBase = QIODevice;
#else
using SyncthingProcessBase = QProcess;
#endif
class LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingProcess : public SyncthingProcessBase {
Q_OBJECT
Q_PROPERTY(bool running READ isRunning)
Q_PROPERTY(CppUtilities::DateTime activeSince READ activeSince)
@ -19,6 +31,7 @@ class LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingProcess : public QProcess {
public:
explicit SyncthingProcess(QObject *parent = nullptr);
~SyncthingProcess() override;
bool isRunning() const;
CppUtilities::DateTime activeSince() const;
bool isActiveFor(unsigned int atLeastSeconds) const;
@ -26,26 +39,62 @@ public:
static SyncthingProcess *mainInstance();
static void setMainInstance(SyncthingProcess *mainInstance);
static QStringList splitArguments(const QString &arguments);
Q_SIGNALS:
void confirmKill();
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
QProcess::ProcessState state() const;
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode openMode = QIODevice::ReadOnly);
qint64 bytesAvailable() const override;
void close() override;
int exitCode() const;
bool waitForFinished(int msecs = 30000);
qint64 processId() const;
QString program() const;
QStringList arguments() const;
#endif
public Q_SLOTS:
void restartSyncthing(const QString &program, const QStringList &arguments);
void startSyncthing(const QString &program, const QStringList &arguments);
void stopSyncthing();
void killSyncthing();
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
void terminate();
void kill();
#endif
Q_SIGNALS:
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
void started();
void finished(int exitCode, QProcess::ExitStatus exitStatus);
void errorOccurred(QProcess::ProcessError error);
void stateChanged(QProcess::ProcessState newState);
#endif
void confirmKill();
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
protected:
qint64 readData(char *data, qint64 maxSize) override;
qint64 writeData(const char *data, qint64 len) override;
#endif
private Q_SLOTS:
void handleStarted();
void handleFinished(int exitCode, QProcess::ExitStatus exitStatus);
void killToRestart();
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
void handleError(QProcess::ProcessError error, const QString &errorMessage, bool closed);
void bufferOutput();
void handleLeftoverProcesses();
#endif
private:
QString m_program;
QStringList m_arguments;
CppUtilities::DateTime m_activeSince;
QTimer m_killTimer;
#ifdef LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS
std::shared_ptr<SyncthingProcessInternalData> m_process;
std::unique_ptr<SyncthingProcessIOHandler> m_handler;
#endif
bool m_manuallyStopped;
static SyncthingProcess *s_mainInstance;
};

View File

@ -7,7 +7,9 @@ function handle_int {
}
trap "handle_int" SIGINT SIGTERM
i=0
while [[ true ]]; do
echo $RANDOM
echo "$i : $RANDOM"
i=$((i + 1))
sleep 1
done

View File

@ -94,13 +94,14 @@ void SyncthingTestInstance::start()
*/
void SyncthingTestInstance::stop()
{
if (m_syncthingProcess.state() == QProcess::Running) {
if (m_syncthingProcess.isRunning()) {
cerr << "\n - Waiting for Syncthing to terminate ..." << endl;
m_syncthingProcess.terminate();
m_syncthingProcess.waitForFinished();
}
if (m_syncthingProcess.isOpen()) {
cerr << "\n - Syncthing terminated with exit code " << m_syncthingProcess.exitCode() << ".\n";
/*
const auto stdOut(m_syncthingProcess.readAllStandardOutput());
if (!stdOut.isEmpty()) {
cerr << "\n - Syncthing stdout during the testrun:\n" << stdOut.data();
@ -114,6 +115,7 @@ void SyncthingTestInstance::stop()
cerr << "\n - Syncthing exited: " << stdOut.count("INFO: Syncthing exited: exit status") << " times";
cerr << "\n - Syncthing panicked: " << stdOut.count("WARNING: Panic detected") << " times";
}
*/
}
}
@ -126,7 +128,7 @@ void SyncthingTestInstance::setInterleavedOutputEnabled(bool interleavedOutputEn
return;
}
m_interleavedOutput = interleavedOutputEnabled;
m_syncthingProcess.setProcessChannelMode(interleavedOutputEnabled ? QProcess::ForwardedChannels : QProcess::SeparateChannels);
//m_syncthingProcess.setProcessChannelMode(interleavedOutputEnabled ? QProcess::ForwardedChannels : QProcess::SeparateChannels);
}
/*!

View File

@ -25,7 +25,7 @@ public:
const QString &apiKey() const;
const QString &syncthingPort() const;
QCoreApplication &application();
QProcess &syncthingProcess();
Data::SyncthingProcess &syncthingProcess();
public Q_SLOTS:
void start();
@ -57,7 +57,7 @@ inline QCoreApplication &SyncthingTestInstance::application()
return m_app;
}
inline QProcess &SyncthingTestInstance::syncthingProcess()
inline Data::SyncthingProcess &SyncthingTestInstance::syncthingProcess()
{
return m_syncthingProcess;
}

View File

@ -19,8 +19,9 @@ int main(int argc, char **argv)
SyncthingTestInstance testInstance;
auto &syncthingProcess(testInstance.syncthingProcess());
syncthingProcess.setProcessChannelMode(QProcess::ForwardedChannels);
QObject::connect(&syncthingProcess, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), &QCoreApplication::exit);
//syncthingProcess.setProcessChannelMode(QProcess::ForwardedChannels);
QObject::connect(&syncthingProcess, static_cast<void (Data::SyncthingProcess::*)(int, QProcess::ExitStatus)>(&Data::SyncthingProcess::finished),
&QCoreApplication::exit);
testInstance.start();
const int res = testInstance.application().exec();

View File

@ -59,11 +59,11 @@ void SyncthingKiller::confirmKill() const
msgBox->addButton(tr("Keep running"), QMessageBox::RejectRole);
msgBox->addButton(tr("Kill process"), QMessageBox::AcceptRole);
connect(process,
#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) || QT_DEPRECATED_SINCE(5, 13)
#if !defined(LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS) && (QT_VERSION < QT_VERSION_CHECK(5, 13, 0) || QT_DEPRECATED_SINCE(5, 13))
static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(
#endif
&SyncthingProcess::finished
#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) || QT_DEPRECATED_SINCE(5, 13)
#if !defined(LIB_SYNCTHING_CONNECTOR_BOOST_PROCESS) && (QT_VERSION < QT_VERSION_CHECK(5, 13, 0) || QT_DEPRECATED_SINCE(5, 13))
)
#endif
,

View File

@ -39,8 +39,8 @@ SyncthingLauncher::SyncthingLauncher(QObject *parent)
connect(&m_process, &SyncthingProcess::readyRead, this, &SyncthingLauncher::handleProcessReadyRead);
connect(&m_process, static_cast<void (SyncthingProcess::*)(int exitCode, QProcess::ExitStatus exitStatus)>(&SyncthingProcess::finished), this,
&SyncthingLauncher::handleProcessFinished);
connect(&m_process, &SyncthingProcess::stateChanged, this, &SyncthingLauncher::handleProcessStateChanged);
connect(&m_process, &SyncthingProcess::errorOccurred, this, &SyncthingLauncher::errorOccurred);
connect(&m_process, &SyncthingProcess::stateChanged, this, &SyncthingLauncher::handleProcessStateChanged, Qt::QueuedConnection);
connect(&m_process, &SyncthingProcess::errorOccurred, this, &SyncthingLauncher::errorOccurred, Qt::QueuedConnection);
connect(&m_process, &SyncthingProcess::confirmKill, this, &SyncthingLauncher::confirmKill);
}

View File

@ -34,6 +34,7 @@ public:
bool isManuallyStopped() const;
bool isEmittingOutput() const;
void setEmittingOutput(bool emittingOutput);
QString errorString() const;
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
LibSyncthing::LogLevel libSyncthingLogLevel() const;
void setLibSyncthingLogLevel(LibSyncthing::LogLevel logLevel);
@ -129,6 +130,12 @@ inline bool SyncthingLauncher::isEmittingOutput() const
return m_emittingOutput;
}
/// \brief Returns the last error message.
inline QString SyncthingLauncher::errorString() const
{
return m_process.errorString();
}
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
/// \brief Returns the log level used for libsyncthing.
inline LibSyncthing::LogLevel SyncthingLauncher::libSyncthingLogLevel() const

View File

@ -80,7 +80,7 @@ struct SYNCTHINGWIDGETS_EXPORT Launcher {
#else
QStringLiteral("syncthing");
#endif
QString syncthingArgs = QStringLiteral("-no-browser -no-restart -logflags=3");
QString syncthingArgs = QStringLiteral("-no-browser -no-console -no-restart -logflags=3");
QHash<QString, ToolParameter> tools;
bool considerForReconnect = false;
bool showButton = false;

View File

@ -1094,25 +1094,32 @@ void LauncherOptionPage::handleSyncthingError(QProcess::ProcessError error)
cursor.movePosition(QTextCursor::End);
cursor.insertBlock();
QString errorString;
switch (error) {
case QProcess::FailedToStart:
errorString = tr("failed to start (e.g. executable does not exist or not permission error)");
break;
case QProcess::Crashed:
errorString = tr("process crashed");
break;
case QProcess::Timedout:
errorString = tr("timeout error");
break;
case QProcess::ReadError:
errorString = tr("read error");
break;
case QProcess::WriteError:
errorString = tr("write error");
break;
default:
errorString = tr("unknown process error");
auto errorString = QString();
if (m_launcher) {
errorString = m_launcher->errorString();
} else if (m_process) {
errorString = m_process->errorString();
}
if (errorString.isEmpty()) {
switch (error) {
case QProcess::FailedToStart:
errorString = tr("failed to start (e.g. executable does not exist or not permission error)");
break;
case QProcess::Crashed:
errorString = tr("process crashed");
break;
case QProcess::Timedout:
errorString = tr("timeout error");
break;
case QProcess::ReadError:
errorString = tr("read error");
break;
case QProcess::WriteError:
errorString = tr("write error");
break;
default:
errorString = tr("unknown process error");
}
}
cursor.insertText(tr("An error occurred when running %1: %2").arg(m_tool.isEmpty() ? QStringLiteral("Syncthing") : m_tool, errorString));
cursor.insertBlock();