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:
parent
7f55afc51c
commit
9ce9b11ba4
|
@ -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})
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue