203 lines
5.8 KiB
C++
203 lines
5.8 KiB
C++
#pragma once
|
|||
|
|
|
||
|
|
#include <QCoreApplication>
|
||
|
|
#include <QDebug>
|
||
|
|
#include <QDir>
|
||
|
|
#include <QFileInfo>
|
||
|
|
#include <QMutex>
|
||
|
|
#include <QMutexLocker>
|
||
|
|
#include <QProcess>
|
||
|
|
#include <QString>
|
||
|
|
#include <QStringList>
|
||
|
|
#include <QThread>
|
||
|
|
#include <QTimer>
|
||
|
|
#include <QWaitCondition>
|
||
|
|
#include <atomic>
|
||
|
|
#include <functional>
|
||
|
|
#include <windows.h>
|
||
|
|
|
||
|
|
class RpcExeProcessThread : public QThread {
|
||
|
|
public:
|
||
|
|
explicit RpcExeProcessThread(QObject* parent = nullptr)
|
||
|
|
: QThread(parent) {}
|
||
|
|
|
||
|
|
~RpcExeProcessThread() override {
|
||
|
|
stopExe();
|
||
|
|
}
|
||
|
|
|
||
|
|
bool startExe(const QString& exePath, const QStringList& args = QStringList()) {
|
||
|
|
if (isRunning()) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
exePath_ = resolvePath(exePath);
|
||
|
|
args_ = args;
|
||
|
|
stopping_.store(false);
|
||
|
|
{
|
||
|
|
QMutexLocker locker(&stateMutex_);
|
||
|
|
startAttempted_ = false;
|
||
|
|
startedOk_ = false;
|
||
|
|
startError_.clear();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!QFileInfo::exists(exePath_)) {
|
||
|
|
qCritical() << "RPC server exe not found:" << exePath_;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
start();
|
||
|
|
QMutexLocker locker(&stateMutex_);
|
||
|
|
if (!startAttempted_) {
|
||
|
|
startCondition_.wait(&stateMutex_, 1500);
|
||
|
|
}
|
||
|
|
if (!startedOk_) {
|
||
|
|
qWarning() << "RPC server process did not start:" << exePath_ << startError_;
|
||
|
|
}
|
||
|
|
return startedOk_;
|
||
|
|
}
|
||
|
|
|
||
|
|
void stopExe(int timeoutMs = 1500) {
|
||
|
|
stopping_.store(true);
|
||
|
|
if (isRunning() && !wait(timeoutMs)) {
|
||
|
|
qWarning() << "RPC server process thread stop timeout:" << exePath_;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
QString exePath() const {
|
||
|
|
return exePath_;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected:
|
||
|
|
void run() override {
|
||
|
|
QProcess process;
|
||
|
|
HANDLE jobHandle = nullptr;
|
||
|
|
|
||
|
|
auto closeJobHandle = [&]() {
|
||
|
|
if (jobHandle) {
|
||
|
|
CloseHandle(jobHandle);
|
||
|
|
jobHandle = nullptr;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
auto assignProcessToJob = [&]() {
|
||
|
|
closeJobHandle();
|
||
|
|
const DWORD pid = process.processId();
|
||
|
|
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
|
||
|
|
if (!hProcess) {
|
||
|
|
qWarning() << "Failed to open RPC server process:" << exePath_;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
jobHandle = CreateJobObject(nullptr, nullptr);
|
||
|
|
if (!jobHandle) {
|
||
|
|
qWarning() << "Failed to create job object";
|
||
|
|
CloseHandle(hProcess);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {};
|
||
|
|
jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
||
|
|
SetInformationJobObject(jobHandle, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli));
|
||
|
|
|
||
|
|
if (!AssignProcessToJobObject(jobHandle, hProcess)) {
|
||
|
|
qWarning() << "Failed to assign process to job object";
|
||
|
|
}
|
||
|
|
CloseHandle(hProcess);
|
||
|
|
};
|
||
|
|
|
||
|
|
std::function<void()> startChild;
|
||
|
|
startChild = [&]() {
|
||
|
|
if (stopping_.load()) {
|
||
|
|
quit();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (process.state() != QProcess::NotRunning) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
process.setProgram(exePath_);
|
||
|
|
process.setArguments(args_);
|
||
|
|
process.start();
|
||
|
|
const bool ok = process.waitForStarted(1000);
|
||
|
|
{
|
||
|
|
QMutexLocker locker(&stateMutex_);
|
||
|
|
startAttempted_ = true;
|
||
|
|
startedOk_ = ok;
|
||
|
|
startError_ = ok ? QString() : process.errorString();
|
||
|
|
startCondition_.wakeAll();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!ok) {
|
||
|
|
qWarning() << "Process failed to start:" << exePath_ << process.errorString();
|
||
|
|
if (!stopping_.load()) {
|
||
|
|
QTimer::singleShot(1000, &process, startChild);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
qDebug() << "RPC server process started:" << exePath_;
|
||
|
|
assignProcessToJob();
|
||
|
|
};
|
||
|
|
|
||
|
|
QObject::connect(&process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
||
|
|
&process, [&](int exitCode, QProcess::ExitStatus exitStatus) {
|
||
|
|
qWarning() << "RPC server process finished:" << exePath_
|
||
|
|
<< "exitCode:" << exitCode << "exitStatus:" << exitStatus;
|
||
|
|
closeJobHandle();
|
||
|
|
if (stopping_.load()) {
|
||
|
|
quit();
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
QTimer::singleShot(1000, &process, startChild);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
QTimer stopTimer;
|
||
|
|
stopTimer.setInterval(50);
|
||
|
|
QObject::connect(&stopTimer, &QTimer::timeout, &process, [&]() {
|
||
|
|
if (!stopping_.load()) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (process.state() == QProcess::Running) {
|
||
|
|
process.kill();
|
||
|
|
process.waitForFinished(500);
|
||
|
|
}
|
||
|
|
closeJobHandle();
|
||
|
|
quit();
|
||
|
|
});
|
||
|
|
|
||
|
|
stopTimer.start();
|
||
|
|
startChild();
|
||
|
|
exec();
|
||
|
|
|
||
|
|
if (process.state() == QProcess::Running) {
|
||
|
|
process.kill();
|
||
|
|
process.waitForFinished(500);
|
||
|
|
}
|
||
|
|
closeJobHandle();
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
QString resolvePath(const QString& exePath) const {
|
||
|
|
QFileInfo exeInfo(exePath);
|
||
|
|
if (!exeInfo.isRelative()) {
|
||
|
|
return exePath;
|
||
|
|
}
|
||
|
|
|
||
|
|
QString resolved = QCoreApplication::applicationDirPath() + "/" + exePath;
|
||
|
|
if (!QFileInfo::exists(resolved)) {
|
||
|
|
resolved = QDir::currentPath() + "/" + exePath;
|
||
|
|
}
|
||
|
|
return resolved;
|
||
|
|
}
|
||
|
|
|
||
|
|
QString exePath_;
|
||
|
|
QStringList args_;
|
||
|
|
std::atomic<bool> stopping_{ true };
|
||
|
|
QMutex stateMutex_;
|
||
|
|
QWaitCondition startCondition_;
|
||
|
|
bool startAttempted_ = false;
|
||
|
|
bool startedOk_ = false;
|
||
|
|
QString startError_;
|
||
|
|
};
|